Unlisted
Edited
May 8, 2024
Insert cell
Insert cell
Insert cell
income = d3.csvParse(
await FileAttachment("income@1.csv").text(),
d3.autoType
)
Insert cell
// Load candidate data
candidates = d3.csvParse(
await FileAttachment("candidates.csv").text(),
d3.autoType
)
Insert cell
// Load data
data = d3.csvParse(
await FileAttachment("viz_project@14.csv").text(),
d3.autoType
)
Insert cell
Insert cell
// Load shapefile
shapefile = d3.json("https://cdn.jsdelivr.net/npm/us-atlas@3/states-albers-10m.json")
Insert cell
// Create a path drawing function
path = d3.geoPath()
Insert cell
Insert cell
<svg id='mapSVG' width=${width} height="800" text-anchor="middle" font-family='sans-serif' font-size='12.5px'>
</svg>
Insert cell
map = d3.select(mapSVG)
Insert cell
// Let's make a map!
map1 = {
// Set up background color
map.style("background-color", republicanVotes < democratVotes ? "lightblue": "rosybrown");
/////////////////////////////////////////////// Assign variables
let select_year2 = 1988
let select_var = "inc_past_4"
// Filter down to most recent, grab columns of interest
var most_recent = data.filter(d => d.Year === select_year2);
// Represent as a Map object to more easily access the values for each state
var most_recent_obj = new Map(most_recent.map(d => [d.State, +d.margin]))
// Make constant values that are necessary
var max_margin = d3.max(most_recent, d => Math.abs(+d.margin))
var max_var = d3.max(data, d => Math.abs(+d[select_var]))
var avg_inc_past = d3.max(income.filter(d => d.Year === select_year2), d => d[select_var])
// Create a diverging color scale
var color = d3.scaleSequential([max_margin, -max_margin], d3.interpolateRdBu)
// Create the color scale for the state winners
const color2 = value => {
// If the value is positive or zero, return red
if (value >= 0) {
return "red";
} else {
// If the value is negative, return blue
return "blue";
}
};
// Get the counts of Republican electoral votes
var republicanVotes = most_recent.reduce((total, obj) => {
// Check if the 'Party' is 'R' (Republican)
if (obj.Party === 'R') {
// Add the value of the 'Votes' column to the total
return total + obj.Votes;
} else {
// If 'Party' is not 'R', return the current total unchanged
return total;
}
}, 0); // Start with a total of 0
// Get the counts of Republican electoral votes
var democratVotes = most_recent.reduce((total, obj) => {
// Check if the 'Party' is 'D'
if (obj.Party === 'D') {
// Add the value of the 'Votes' column to the total
return total + obj.Votes;
} else {
// If 'Party' is not 'D', return the current total unchanged
return total;
}
}, 0); // Start with a total of 0
// Get candidate names
var candidates2 = candidates.filter(d => d.Year === select_year2);
var dem_can = candidates2.filter(d => d.party_simplified == "DEMOCRAT")
.map(d => d.candidate);
var rep_can = candidates2.filter(d => d.party_simplified == "REPUBLICAN")
.map(d => d.candidate);
/////////////////////////////////////////////// SVG and its parts
// Add paths for each state
const paths = map.selectAll("path")
.data(topojson.feature(shapefile, shapefile.objects.states).features)
.join("path")
.attr("d", path)
.attr("fill", d => {
return color2(most_recent_obj.get(d.properties.name));
})
.style("stroke", "black") // Add black stroke color
.style("stroke-width", 1) // Add stroke width
.style("vector-effect", "non-scaling-stroke") // Prevent stroke width from scaling with zoom
.on("mouseover", function(event, d) {
const stateData = most_recent.find(state => state.State === d.properties.name);
// Fade other circles by reducing opacity
map.selectAll("circle")
.transition()
.duration(150)
.style("opacity", circle => (circle.State === d.properties.name ? 1 : 0.05));

// Fade other states by reducing opacity
map.selectAll("path")
.transition()
.duration(150)
.style("opacity", state => (state.State === d.properties.name ? 1 : 0.2));

// Calculate position for tooltip
const tooltipX = path.centroid(d)[0] + 100; // Adjust x position to the right of the state's centroid
const tooltipY = path.centroid(d)[1]; // Keep y position same as state's centroid

// Append tooltip text box to the SVG on mouseover
const tooltipBox = map.append("g")
.attr("class", "tooltip-box")
.attr("transform", `translate(${tooltipX}, ${tooltipY})`);

tooltipBox.append("rect")
.attr("width", 160) // Adjust width as needed
.attr("height", 75) // Adjust height as needed
.attr("x", -75) // Adjust x position to center the box
.attr("y", -40) // Adjust y position to center the box
.style("fill", "white")
.style("stroke", "black");

// Append tooltip text to the tooltip box
tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("State: " + d.properties.name)
.attr("y", "-25"); // Adjust vertical position of text

tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("Change in Income: " + Math.round(10 * stateData[select_var]) / 10 + "%")
.attr("y", "-10"); // Adjust vertical position of text

tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("Margin of Victory: " + Math.abs(Math.round(10 * stateData.margin) / 10) + "%" + (stateData.margin < 0 ? " D" : " R"))
.attr("y", "5"); // Adjust vertical position of text

tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("Electoral Votes: " + stateData.Votes)
.attr("y", "20"); // Adjust vertical position of text
})
.on("mouseout", function() {
// Restore state opacity
map.selectAll("path")
.transition()
.duration(100)
.style("opacity", 1);

// Restore opacity of all circles on mouseout
map.selectAll("circle")
.transition()
.duration(100)
.style("opacity", 1);

// Remove tooltip text from the SVG on mouseout
map.selectAll(".tooltip-box").remove(); // Remove elements with the "tooltip-box" class
});
// Add text for each state
const labels = map.selectAll("text")
.data(topojson.feature(shapefile, shapefile.objects.states).features)
.join("text")
.text(d => {
const stateData = most_recent.find(state => state.State === d.properties.name);
return stateData ? stateData.Votes : ""; // Display electoral votes or empty string if not found
})
.attr("y", d => d.properties.name === "District of Columbia" ? path.centroid(d)[1] + 5 :
(d.properties.name === "Michigan" ? path.centroid(d)[1] + 10 : path.centroid(d)[1]))
.attr("x", d => d.properties.name === "District of Columbia" ? path.centroid(d)[0] - 5 :
(d.properties.name === "Florida" ? path.centroid(d)[0] + 10 :
(d.properties.name === "Louisiana" ? path.centroid(d)[0] - 13 : path.centroid(d)[0])))
.style("fill", "white")
.style("font-size", "13px")
.on("mouseover", function(event, d) {
const stateData = most_recent.find(state => state.State === d.properties.name);
// Fade other circles by reducing opacity
map.selectAll("circle")
.transition()
.duration(150)
.style("opacity", circle => (circle.State === d.properties.name ? 1 : 0.05));

// Fade other states by reducing opacity
map.selectAll("path")
.transition()
.duration(150)
.style("opacity", state => (state.State === d.properties.name ? 1 : 0.2));

// Calculate position for tooltip
const tooltipX = path.centroid(d)[0] + 100; // Adjust x position to the right of the state's centroid
const tooltipY = path.centroid(d)[1]; // Keep y position same as state's centroid

// Append tooltip text box to the SVG on mouseover
const tooltipBox = map.append("g")
.attr("class", "tooltip-box")
.attr("transform", `translate(${tooltipX}, ${tooltipY})`);

tooltipBox.append("rect")
.attr("width", 160) // Adjust width as needed
.attr("height", 75) // Adjust height as needed
.attr("x", -75) // Adjust x position to center the box
.attr("y", -40) // Adjust y position to center the box
.style("fill", "white")
.style("stroke", "black");

// Append tooltip text to the tooltip box
tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("State: " + d.properties.name)
.attr("y", "-25"); // Adjust vertical position of text

tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("Change in Income: " + Math.round(10 * stateData[select_var]) / 10 + "%")
.attr("y", "-10"); // Adjust vertical position of text

tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("Margin of Victory: " + Math.abs(Math.round(10 * stateData.margin) / 10) + "%" + (stateData.margin < 0 ? " D" : " R"))
.attr("y", "5"); // Adjust vertical position of text

tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("Electoral Votes: " + stateData.Votes)
.attr("y", "20"); // Adjust vertical position of text
})
.on("mouseout", function() {
// Restore state opacity
map.selectAll("path")
.transition()
.duration(100)
.style("opacity", 1);

// Restore opacity of all circles on mouseout
map.selectAll("circle")
.transition()
.duration(100)
.style("opacity", 1);

// Remove tooltip text from the SVG on mouseout
map.selectAll(".tooltip-box").remove(); // Remove elements with the "tooltip-box" class
});

// Add a title
const title = map.append("text")
.attr('x', width/2)
.attr("y", 40)
.text(select_year2 + ' Election Results')
.style("font-size", "28px")
.style("font-weight", "bold")

// Add a subtitle
const subtitle = map.append("text")
.attr("x", width/2)
.attr("y", 60)
.text("by electoral votes")
.style("font-size", "12px")
.style("font-weight", "bold")

// Add a legend
const legend = map.append("g")
.attr("transform", `translate(${width - 400}, 20)`);

legend.append("text")
.attr("y", 70)
.style("font-weight", "bold")
.style("font-size", "16px")
.text("Party");

legend.append("rect")
.attr("x", -40)
.attr("y", 80)
.attr("width", 20)
.attr("height", 20)
.style("fill", "red");

legend.append("text")
.attr("x", 15)
.attr("y", 95)
.text("Republican");

legend.append("rect")
.attr("x", -40)
.attr("y", 110)
.attr("width", 20)
.attr("height", 20)
.style("fill", "blue");

legend.append("text")
.attr("x", 11)
.attr("y", 125)
.text("Democrat");
/////////////////////////////////////////////// Add electoral votes bar legend
// Add electoral votes bar legend
const legend2 = map.append("g")
.attr("transform", `translate(${width - 200}, 0)`);

legend2.append("text")
.attr("x", 20)
.attr("y", 250)
.style("font-weight", "bold")
.style("font-size", "14px")
.text("Electoral Votes");

// Add electoral votes bars for Republican
legend2.append("rect")
.attr("x", 0)
.attr("y", 260)
.attr("width", 50)
.attr("height", republicanVotes/2)
.style("fill", "red")
.style("stroke", "black");

legend2.append("text")
.attr("x", 25)
.attr("y", 260 + republicanVotes/4)
.style("fill", "white")
.style("font-size", "14px")
.text(republicanVotes); // Display number of votes

legend2.append("text")
.attr("x", 85)
.attr("y", 260 + republicanVotes/4)
.style("font-size", "14px")
.text(rep_can); // Display candidate

// Add electoral votes bars for Democrat
legend2.append("rect")
.attr("x", 0)
.attr("y", 260 + republicanVotes/2)
.attr("width", 50)
.attr("height", democratVotes/2)
.style("fill", "blue")
.style("stroke", "black");

legend2.append("text")
.attr("x", 25)
.attr("y", 260 + republicanVotes/2 + democratVotes/4)
.style("fill", "white")
.style("font-size", "14px")
.text(democratVotes); // Display number of votes
legend2.append("text")
.attr("x", 85)
.attr("y", 260 + republicanVotes/2 + democratVotes/4)
.style("font-size", "14px")
.text(dem_can); // Display candidate

// Add 270 to win line
map.append("line")
.attr("x1", width - 200)
.attr("y1", 260 + 538/4)
.attr("x2", width - 150)
.attr("y2", 260 + 538/4)
.style("stroke", "black")
.style("stroke-width", 1.5);

map.append("text")
.attr("x", width - 230)
.attr("y", 255 + 538/4)
.text("270 votes")
.style("font-size", "12px")
.style("fill", "black");

map.append("text")
.attr("x", width - 230)
.attr("y", 265 + 538/4)
.text("to win")
.style("font-size", "12px")
.style("fill", "black");
/////////////////////////////////////////////// Bubble bar
// Add a bubble bar with all of the states
const bubble_bar = map.append("g");

bubble_bar.append("line") // Line for all of the bubbles to be on
.attr("x1", width * .15)
.attr("y1", 730)
.attr("x2", width * .85)
.attr("y2", 730)
.style("stroke", "black")
.style("stroke-width", 1.5);

bubble_bar.append("line") // Line for the minimum value
.attr("x1", width * .15)
.attr("y1", 715)
.attr("x2", width * .15)
.attr("y2", 745)
.style("stroke", "black")
.style("stroke-width", .7);

bubble_bar.append("line") // Line for the maximum value
.attr("x1", width * .85)
.attr("y1", 715)
.attr("x2", width * .85)
.attr("y2", 745)
.style("stroke", "black")
.style("stroke-width", .7);

bubble_bar.append("line") // Line for 0% change
.attr("x1", width/2)
.attr("y1", 715)
.attr("x2", width/2)
.attr("y2", 745)
.style("stroke", "black")
.style("stroke-width", .7);

bubble_bar.append("line") // Line for US Avg
.attr("x1", (width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)))
.attr("y1", 715)
.attr("x2", (width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)))
.attr("y2", 758)
.style("stroke", "white")
.style("stroke-width", .8);

bubble_bar.append("text") // Text for the minimum
.attr("x", width * .15 + 2)
.attr("y", 758)
.text(-1 * Math.round(max_var * 10)/10 + "%")
.style("font-size", "12px")
.style("fill", "black");

bubble_bar.append("text") // Text for the maximum
.attr("x", width * .85 + 2)
.attr("y", 758)
.text(Math.round(max_var * 10)/10 + "%")
.style("font-size", "12px")
.style("fill", "black");

bubble_bar.append("text") // Text for 0% change
.attr("x", width/2 + 4.3)
.attr("y", 758)
.text("0.0%")
.style("font-size", "12px")
.style("fill", "black");

bubble_bar.append("text") // Text for US avg
.attr("x", (width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)) + 2)
.attr("y", 771)
.text("US Average")
.style("font-size", "12px")
.style("fill", "white");

bubble_bar.append("text")
.attr("x", width/2)
.attr("y", 680)
.text("Change in Real Income Past Four Years")
.style("font-size", "16px")
.style("fill", "black");

bubble_bar.append("text")
.attr("x", width/2)
.attr("y", 700)
.text("Colored By Margin of Victory")
.style("font-size", "12px")
.style("fill", "black");

// Add circles for each state
const circles = map.selectAll("circle")
.data(most_recent)
.enter()
.append("circle")
.attr("cx", d => {
// Calculate the x-coordinate based on the inc_past value and the width of the line
return (width * 0.15) + (((max_var + d[select_var]) / (max_var * 2)) * (width * 0.7));
})
.attr("cy", 730) // y-coordinate on the line
.attr("r", 8.5) // Adjust the radius of the circles
.style("fill", d => color(d.margin)) // Set the fill color based on the margin value
.on("mouseover", function(event, d) {
// Fade other circles by reducing opacity
map.selectAll("circle")
.transition()
.duration(150)
.style("opacity", circle => (circle === d ? 1 : 0.05));
// Fade other states by reducing opacity
map.selectAll("path")
.transition()
.duration(150)
.style("opacity", state => (state.properties.name === d.State ? 1 : 0.2));
// Append tooltip text box to the SVG on mouseover
const tooltipBox = map.append("g")
.attr("class", "tooltip-box")
.attr("transform", `translate(${(width * 0.15) + (((max_var + d[select_var]) / (max_var * 2)) * (width * 0.7))}, 680)`);
tooltipBox.append("rect")
.attr("width", 160) // Adjust width as needed
.attr("height", 75) // Adjust height as needed
.attr("x", -75) // Adjust x position to center the box
.attr("y", -40) // Adjust y position to center the box
.style("fill", "white")
.style("stroke", "black");
// Append tooltip text to the tooltip box
tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("State: " + d.State)
.attr("dy", "-25"); // Adjust vertical position of text
tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("Change in Income: " + Math.round(10 * d[select_var]) / 10 + "%")
.attr("dy", "-10"); // Adjust vertical position of text
tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("Margin of Victory: " + Math.abs(Math.round(10 * d.margin) / 10) + "%" + (d.margin < 0 ? " D" : " R"))
.attr("dy", "5"); // Adjust vertical position of text

tooltipBox.append("text")
.style("font-size", "12px")
.style("fill", "black")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.text("Electoral Votes: " + d.Votes)
.attr("dy", "20"); // Adjust vertical position of text
})
.on("mouseout", function() {
// Restore state opacity
map.selectAll("path")
.transition()
.duration(100)
.style("opacity", 1);
// Restore opacity of all circles on mouseout
map.selectAll("circle")
.transition()
.duration(100)
.style("opacity", 1);
// Remove tooltip text from the SVG on mouseout
map.selectAll(".tooltip-box").remove(); // Remove elements with the "tooltip-box" class
});

////////////////
const btns = map.append("g");
function updateMap() {
(select_year2 === 2016) ? (select_year2 = 1988) : select_year2 += 4
var max_var = d3.max(data, d => Math.abs(+d[select_var]));
// Filter down to the data for the new year
var most_recent = data.filter(d => d.Year === select_year2);
// Represent as a Map object to more easily access the values for each state
var most_recent_obj = new Map(most_recent.map(d => [d.State, +d.margin]))
// Get candidate names for the new year
var candidates2 = candidates.filter(d => d.Year === select_year2);
var dem_can = candidates2.filter(d => d.party_simplified == "DEMOCRAT")
.map(d => d.candidate);
var rep_can = candidates2.filter(d => d.party_simplified == "REPUBLICAN")
.map(d => d.candidate);
var max_margin = d3.max(most_recent, d => Math.abs(+d.margin))
var avg_inc_past = d3.max(income.filter(d => d.Year === select_year2), d => d[select_var])
var color = d3.scaleSequential([max_margin, -max_margin], d3.interpolateRdBu)
var republicanVotes = most_recent.reduce((total, obj) => {
// Check if the 'Party' is 'R' (Republican)
if (obj.Party === 'R') {
// Add the value of the 'Votes' column to the total
return total + obj.Votes;
} else {
// If 'Party' is not 'R', return the current total unchanged
return total;
}
}, 0); // Start with a total of 0
var democratVotes = most_recent.reduce((total, obj) => {
// Check if the 'Party' is 'D'
if (obj.Party === 'D') {
// Add the value of the 'Votes' column to the total
return total + obj.Votes;
} else {
// If 'Party' is not 'D', return the current total unchanged
return total;
}
}, 0);
// Update the title with the new year
title.text(select_year2 + ' Election Results');
// Update paths with new data
paths.data(topojson.feature(shapefile, shapefile.objects.states).features)
.transition()
.duration(600)
.attr("fill", d => {
return color2(most_recent_obj.get(d.properties.name));
});
// Update labels with new data
labels.data(topojson.feature(shapefile, shapefile.objects.states).features)
.transition()
.duration(600)
.text(d => {
const stateData = most_recent.find(state => state.State === d.properties.name);
return stateData ? stateData.Votes : ""; // Display electoral votes or empty string if not found
});
// Update circles with new data
circles.data(most_recent)
.transition()
.duration(600)
.style("fill", d => color(d.margin))
.attr("cx", d => {
// Calculate the x-coordinate based on the inc_past value and the width of the line
return (width * 0.15) + (((max_var + d[select_var]) / (max_var * 2)) * (width * 0.7));
});
bubble_bar.selectAll("text")
.text((d, i) => i === 0 ? (-1 * Math.round(max_var * 10)/10 + "%") :
(i === 1 ? (Math.round(max_var * 10)/10 + "%") :
(i === 2 ? ("0.0%") :
(i === 3 ? ("US Average") :
(i === 4 ? (select_var === "inc_past_2" ? ("Change in Real Income Past Two Years") :
(select_var === "inc_past_1" ? ("Change in Real Income Over Past Year"): ("Change in Real Income Past Four Years"))) :
(i === 5 ? ("Colored By Margin of Victory") : ""))))))
.attr("x", (d, i) => i === 0 ? (width * .15 + 2) :
(i === 1 ? (width * .85 + 2) :
(i === 2 ? (width/2 + 4.3) :
(i === 3 ? ((width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)) + 2) :
(i === 4 ? (width/2) : width/2)))))
bubble_bar.selectAll("line") // Line for US Avg
.attr("x1", (d, i) => i === 0 ? (width * .15) :
(i === 1 ? (width * .15) :
(i === 2 ? (width * .85) :
(i === 3 ? (width/2) : ((width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)))))))
.attr("x2", (d, i) => i === 0 ? (width * .85) :
(i === 1 ? (width * .15) :
(i === 2 ? (width * .85) :
(i === 3 ? (width/2) : ((width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)))))));
// Update electoral votes bars with new data
legend2.selectAll("rect")
.transition()
.duration(600)
.attr("height", (d, i) => i % 2 === 0 ? (republicanVotes/2) : (democratVotes/2))
.attr("y", (d, i) => i % 2 === 0 ? 260 : (260 + republicanVotes/2))
legend2.selectAll("text")
.transition()
.duration(600)
.text((d, i) => i === 0 ? ("Electoral Votes") :
(i === 1 ? (republicanVotes) :
(i === 2 ? (rep_can) :
(i === 3 ? (democratVotes) : dem_can))))
.attr("y", (d, i) => i === 0 ? 250 :
(i === 1 ? 260 + republicanVotes/4 :
(i === 2 ? 260 + republicanVotes/4 :
(i === 3 ? 260 + republicanVotes/2 + democratVotes/4 : 260 + republicanVotes/2 + democratVotes/4))));
// Update background color
map.style("background-color", republicanVotes > democratVotes ? "rosybrown" : "lightblue");
};

function updateMap2() {
(select_year2 === 1988) ? (select_year2 = 2016) : select_year2 -= 4
var max_var = d3.max(data, d => Math.abs(+d[select_var]));
// Filter down to the data for the new year
var most_recent = data.filter(d => d.Year === select_year2);
// Represent as a Map object to more easily access the values for each state
var most_recent_obj = new Map(most_recent.map(d => [d.State, +d.margin]))
// Get candidate names for the new year
var candidates2 = candidates.filter(d => d.Year === select_year2);
var dem_can = candidates2.filter(d => d.party_simplified == "DEMOCRAT")
.map(d => d.candidate);
var rep_can = candidates2.filter(d => d.party_simplified == "REPUBLICAN")
.map(d => d.candidate);
var max_margin = d3.max(most_recent, d => Math.abs(+d.margin))
var avg_inc_past = d3.max(income.filter(d => d.Year === select_year2), d => d[select_var])
var color = d3.scaleSequential([max_margin, -max_margin], d3.interpolateRdBu)
var republicanVotes = most_recent.reduce((total, obj) => {
// Check if the 'Party' is 'R' (Republican)
if (obj.Party === 'R') {
// Add the value of the 'Votes' column to the total
return total + obj.Votes;
} else {
// If 'Party' is not 'R', return the current total unchanged
return total;
}
}, 0); // Start with a total of 0
var democratVotes = most_recent.reduce((total, obj) => {
// Check if the 'Party' is 'D'
if (obj.Party === 'D') {
// Add the value of the 'Votes' column to the total
return total + obj.Votes;
} else {
// If 'Party' is not 'D', return the current total unchanged
return total;
}
}, 0);
// Update the title with the new year
title.text(select_year2 + ' Election Results');
// Update paths with new data
paths.data(topojson.feature(shapefile, shapefile.objects.states).features)
.transition()
.duration(600)
.attr("fill", d => {
return color2(most_recent_obj.get(d.properties.name));
});
// Update labels with new data
labels.data(topojson.feature(shapefile, shapefile.objects.states).features)
.transition()
.duration(600)
.text(d => {
const stateData = most_recent.find(state => state.State === d.properties.name);
return stateData ? stateData.Votes : ""; // Display electoral votes or empty string if not found
});
// Update circles with new data
circles.data(most_recent)
.transition()
.duration(600)
.style("fill", d => color(d.margin))
.attr("cx", d => {
// Calculate the x-coordinate based on the inc_past value and the width of the line
return (width * 0.15) + (((max_var + d[select_var]) / (max_var * 2)) * (width * 0.7));
});
bubble_bar.selectAll("text")
.text((d, i) => i === 0 ? (-1 * Math.round(max_var * 10)/10 + "%") :
(i === 1 ? (Math.round(max_var * 10)/10 + "%") :
(i === 2 ? ("0.0%") :
(i === 3 ? ("US Average") :
(i === 4 ? (select_var === "inc_past_2" ? ("Change in Real Income Past Two Years") :
(select_var === "inc_past_1" ? ("Change in Real Income Over Past Year"): ("Change in Real Income Past Four Years"))) :
(i === 5 ? ("Colored By Margin of Victory") : ""))))))
.attr("x", (d, i) => i === 0 ? (width * .15 + 2) :
(i === 1 ? (width * .85 + 2) :
(i === 2 ? (width/2 + 4.3) :
(i === 3 ? ((width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)) + 2) :
(i === 4 ? (width/2) : width/2)))))
bubble_bar.selectAll("line") // Line for US Avg
.attr("x1", (d, i) => i === 0 ? (width * .15) :
(i === 1 ? (width * .15) :
(i === 2 ? (width * .85) :
(i === 3 ? (width/2) : ((width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)))))))
.attr("x2", (d, i) => i === 0 ? (width * .85) :
(i === 1 ? (width * .15) :
(i === 2 ? (width * .85) :
(i === 3 ? (width/2) : ((width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)))))));
// Update electoral votes bars with new data
legend2.selectAll("rect")
.transition()
.duration(600)
.attr("height", (d, i) => i % 2 === 0 ? (republicanVotes/2) : (democratVotes/2))
.attr("y", (d, i) => i % 2 === 0 ? 260 : (260 + republicanVotes/2))
legend2.selectAll("text")
.transition()
.duration(600)
.text((d, i) => i === 0 ? ("Electoral Votes") :
(i === 1 ? (republicanVotes) :
(i === 2 ? (rep_can) :
(i === 3 ? (democratVotes) : dem_can))))
.attr("y", (d, i) => i === 0 ? 250 :
(i === 1 ? 260 + republicanVotes/4 :
(i === 2 ? 260 + republicanVotes/4 :
(i === 3 ? 260 + republicanVotes/2 + democratVotes/4 : 260 + republicanVotes/2 + democratVotes/4))));
// Update background color
map.style("background-color", republicanVotes > democratVotes ? "rosybrown" : "lightblue");
};

function updateMap3(select_var2) {
select_year2 = select_year2 += 4
select_year2 = select_year2 -= 4
var max_var = d3.max(data, d => Math.abs(+d[select_var2]));
// Filter down to the data for the new year
var most_recent = data.filter(d => d.Year === select_year2);
// Represent as a Map object to more easily access the values for each state
var most_recent_obj = new Map(most_recent.map(d => [d.State, +d.margin]))
// Get candidate names for the new year
var candidates2 = candidates.filter(d => d.Year === select_year2);
var dem_can = candidates2.filter(d => d.party_simplified == "DEMOCRAT")
.map(d => d.candidate);
var rep_can = candidates2.filter(d => d.party_simplified == "REPUBLICAN")
.map(d => d.candidate);
var max_margin = d3.max(most_recent, d => Math.abs(+d.margin))
var avg_inc_past = d3.max(income.filter(d => d.Year === select_year2), d => d[select_var2])
var color = d3.scaleSequential([max_margin, -max_margin], d3.interpolateRdBu)
var republicanVotes = most_recent.reduce((total, obj) => {
// Check if the 'Party' is 'R' (Republican)
if (obj.Party === 'R') {
// Add the value of the 'Votes' column to the total
return total + obj.Votes;
} else {
// If 'Party' is not 'R', return the current total unchanged
return total;
}
}, 0); // Start with a total of 0
var democratVotes = most_recent.reduce((total, obj) => {
// Check if the 'Party' is 'D'
if (obj.Party === 'D') {
// Add the value of the 'Votes' column to the total
return total + obj.Votes;
} else {
// If 'Party' is not 'D', return the current total unchanged
return total;
}
}, 0);
// Update the title with the new year
title.text(select_year2 + ' Election Results');
// Update paths with new data
paths.data(topojson.feature(shapefile, shapefile.objects.states).features)
.transition()
.duration(600)
.attr("fill", d => {
return color2(most_recent_obj.get(d.properties.name));
});
// Update labels with new data
labels.data(topojson.feature(shapefile, shapefile.objects.states).features)
.transition()
.duration(600)
.text(d => {
const stateData = most_recent.find(state => state.State === d.properties.name);
return stateData ? stateData.Votes : ""; // Display electoral votes or empty string if not found
});
// Update circles with new data
circles.data(most_recent)
.transition()
.duration(600)
.style("fill", d => color(d.margin))
.attr("cx", d => {
// Calculate the x-coordinate based on the inc_past value and the width of the line
return (width * 0.15) + (((max_var + d[select_var2]) / (max_var * 2)) * (width * 0.7));
});
bubble_bar.selectAll("text")
.text((d, i) => i === 0 ? (-1 * Math.round(max_var * 10)/10 + "%") :
(i === 1 ? (Math.round(max_var * 10)/10 + "%") :
(i === 2 ? ("0.0%") :
(i === 3 ? ("US Average") :
(i === 4 ? (select_var2 === "inc_past_2" ? ("Change in Real Income Past Two Years") :
(select_var2 === "inc_past_1" ? ("Change in Real Income Over Past Year"): ("Change in Real Income Past Four Years"))) :
(i === 5 ? ("Colored By Margin of Victory") : ""))))))
.attr("x", (d, i) => i === 0 ? (width * .15 + 2) :
(i === 1 ? (width * .85 + 2) :
(i === 2 ? (width/2 + 4.3) :
(i === 3 ? ((width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)) + 2) :
(i === 4 ? (width/2) : width/2)))))
bubble_bar.selectAll("line") // Line for US Avg
.attr("x1", (d, i) => i === 0 ? (width * .15) :
(i === 1 ? (width * .15) :
(i === 2 ? (width * .85) :
(i === 3 ? (width/2) : ((width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)))))))
.attr("x2", (d, i) => i === 0 ? (width * .85) :
(i === 1 ? (width * .15) :
(i === 2 ? (width * .85) :
(i === 3 ? (width/2) : ((width * 0.15) + (((max_var + avg_inc_past) / (max_var * 2)) * (width * 0.7)))))));
// Update electoral votes bars with new data
legend2.selectAll("rect")
.transition()
.duration(600)
.attr("height", (d, i) => i % 2 === 0 ? (republicanVotes/2) : (democratVotes/2))
.attr("y", (d, i) => i % 2 === 0 ? 260 : (260 + republicanVotes/2))
legend2.selectAll("text")
.transition()
.duration(600)
.text((d, i) => i === 0 ? ("Electoral Votes") :
(i === 1 ? (republicanVotes) :
(i === 2 ? (rep_can) :
(i === 3 ? (democratVotes) : dem_can))))
.attr("y", (d, i) => i === 0 ? 250 :
(i === 1 ? 260 + republicanVotes/4 :
(i === 2 ? 260 + republicanVotes/4 :
(i === 3 ? 260 + republicanVotes/2 + democratVotes/4 : 260 + republicanVotes/2 + democratVotes/4))));
// Update background color
map.style("background-color", republicanVotes > democratVotes ? "rosybrown" : "lightblue");
};
btns.append("rect")
.attr("x", width - 120)
.attr("y", 740)
.attr("width", 100)
.attr("height", 40)
.style("fill", "lightgrey")
.style("stroke", "black")
.on("click", updateMap);

btns.append("text")
.attr("x", width - 72)
.attr("y", 764)
.text("Next Election")
.style("font-size", "14px")
.style("fill", "black")
.on("click", updateMap);

btns.append("rect")
.attr("x", 20)
.attr("y", 740)
.attr("width", 100)
.attr("height", 40)
.style("fill", "lightgrey")
.style("stroke", "black")
.on("click", updateMap2);

btns.append("text")
.attr("x", 68)
.attr("y", 764)
.text("Last Election")
.style("font-size", "14px")
.style("fill", "black")
.on("click", updateMap2);

function updateVar4() {
select_var = "inc_past_4"
updateMap3(select_var);
};

function updateVar2() {
select_var = "inc_past_2"
updateMap3(select_var);
};

function updateVar1() {
select_var = "inc_past_1"
updateMap3(select_var);
};

btns.append("rect") // Pick change over 4 years
.attr("x", width - 94)
.attr("y", 575)
.attr("width", 82)
.attr("height", 25)
.style("fill", "lightgrey")
.style("stroke", "black")
.on("click", updateVar4);

btns.append("rect") // Pick change over 2 years
.attr("x", width - 94)
.attr("y", 610)
.attr("width", 82)
.attr("height", 25)
.style("fill", "lightgrey")
.style("stroke", "black")
.on("click", updateVar2);

btns.append("rect") // Pick change over 1 year
.attr("x", width - 94)
.attr("y", 645)
.attr("width", 82)
.attr("height", 25)
.style("fill", "lightgrey")
.style("stroke", "black")
.on("click", updateVar1);

btns.append("text") // Text change over 4 years
.attr("x", width - 54)
.attr("y", 590)
.style("font-size", "8px")
.text("Change over 4 years")
.on("click", updateVar4);

btns.append("text") // Text change over 2 years
.attr("x", width - 54)
.attr("y", 625)
.style("font-size", "8px")
.text("Change over 2 years")
.on("click", updateVar2);

btns.append("text") // Text change over 1 year
.attr("x", width - 54)
.attr("y", 660)
.style("font-size", "8px")
.text("Change over 1 year")
.on("click", updateVar1);
// Return svg
return map.node()
}
Insert cell
Insert cell
d3 = require("d3")
Insert cell
import { aq, op } from "@uwdata/arquero"
Insert cell
topojson = require("topojson-client@3")
Insert cell
import { legend} from "@d3/color-legend"
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