Public
Edited
Jul 22, 2024
1 fork
Insert cell
Insert cell
Insert cell
chart = makeSquares(createTestDists(10, 400))
Insert cell
function makeSquares(initialData) {
const updateBtn = document.getElementById("update-btn");

var squareSize = 20;
const nudgeDown = 20;
const nudgeRight = 20;
const titleGap = 5;
const gapBetween = 5;
var halfSquare = squareSize / 2;
var maxCol = 10;
var cur = 0;
var height = 800;
var minRows = 11;
var rows = 40;

const svg = d3.select(DOM.svg(width, height));
const t = svg.transition().duration(750);

var squareGap = squareSize + gapBetween;
// Democrat squares

const drawGraph = (data) => {
const squareUpdate = svg
.selectAll("circle")
.data(data, (d) => d.id)
// .attr("height", squareSize)
// .attr("width", squareSize)
.call((update) =>
update
.transition()
.duration(750)
.style("fill", (d) => colorScaleDemToRep(d.color))
.attr("cx", (d) => d.row * squareGap + nudgeDown)
.attr("cy", (d) => d.col * squareGap + nudgeDown)
.style("stroke", (d) => winnerColor(d.color))
.style("stroke-width", (d) => winnerStroke(d.won, d.color))
);

const squareEnter = squareUpdate
.enter()
.append("circle")
.attr("class", "grid-object")
.attr("r", squareSize / 2)
// .attr("height", squareSize)
// .attr("width", squareSize)
.style("fill", (d) => colorScaleDemToRep(d.color))
.attr("cx", (d) => randomIntFromInterval(nudgeRight, width / 2 - 20))
.attr("cy", (d) => randomIntFromInterval(nudgeDown, height / 2 - 20))
//.attr("class", "scale-dem")
.style("stroke-width", (d) => winnerStroke(d.won))
.attr("stroke", (d) => colorScaleDemToRep(d.color))
.call((enter) =>
enter
.transition(t)
.attr("cx", (d) => d.row * squareGap + nudgeDown)
.attr("cy", (d) => d.col * squareGap + nudgeDown)
.style("stroke", (d) => winnerColor(d.color))
.style("stroke-width", (d) => winnerStroke(d.won, d.color))
);

const squareExit = squareUpdate
.exit()
.call((exit) => exit.transition(t).attr("y", -30).remove());

//await Promises.tick(2000);
};

cur = pickFromFactors(initialData.length, rows, minRows);
drawGraph(initialData);

drawGraph(updateRowCol(initialData, cur));
// Simulate an enter pattern
updateBtn.addEventListener("click", () => {
// Remove the elements before we draw again so we can see the enter pattern
// console.log("redraw", randomIntFromInterval(4, 10));
//cur = notCurrentRand(minRows, rows, cur);
cur = pickFromFactors(initialData.length, rows, minRows);

drawGraph(updateRowCol(initialData, cur));
});

return svg.node();
}
Insert cell
realData = listWinners(getHouseRaces(data))
Insert cell
realDataSorted = updateRowColReal(realData, 10)
Insert cell
realDataFlipped = sortParties(listWinners(getHouseRaces(data)))
Insert cell
function sortParties(data) {
var Reps = [];
var Dems = [];
var NoWinners = [];
for (const race of data) {
if (race && race.winnerParty && race.winnerParty == "GOP") {
if (race.votePerc == 0) {
var tempVote = race;
tempVote.VotePerc = 1;
Reps.push(tempVote);
} else {
Reps.push(race);
}
}
if (race && race.winnerParty && race.winnerParty == "DEM") {
if (race.votePerc == 0) {
var tempVote = race;
tempVote.VotePerc = 1;
Dems.push(tempVote);
} else {
Dems.push(race);
}
}
if (race && race.winnerParty && race.winnerParty == "No Winner") {
NoWinners.push(race);
}
}
var SortedReps = Reps.slice().sort((a, b) =>
d3.descending(a.votePerc, b.votePerc)
);
var SortedNoWinners = NoWinners.slice().sort((a, b) =>
d3.descending(a.votePerc, b.votePerc)
);
var SortedDems = Dems.slice().sort((a, b) =>
d3.ascending(a.votePerc, b.votePerc)
);
console.log(SortedReps);

const combinedArray = [...SortedReps, ...SortedNoWinners, ...SortedDems];

return combinedArray;
}
Insert cell
test = pickFromFactors(100, 60, 12)
Insert cell
function pickFromFactors(num, maxWidth, minWidth) {
var factors = findFactors(num, maxWidth, minWidth);
d3.shuffle(factors);

return factors[0];
}
Insert cell
factors = findFactors(100, 50)
Insert cell
function findFactors(num, max, minWidth = 0) {
var factors = [];

for (let index = minWidth; index < max; ++index) {
if (num % index == 0) factors.push(index);
}
if (factors.length == 0) {
return 0;
}
return factors;
}
Insert cell
function winnerStroke(winner, vote) {
if (vote > 0.55 || vote < 0.45)
if (winner == 1) {
return 3;
}
return 0;
}
Insert cell
function winnerColor(vote) {
if (vote < 0.5) {
return "#045992";
}
return "#a82c18";
}
Insert cell
function rowsAndColsLeftToRight(length, num, numCols) {

var numRows = Math.ceil(length / numCols);
var col = Math.floor(num / numRows);
var temp = num - numRows * col;

return [col, temp];
}
Insert cell
function notCurrentRand(min, max, cur) {
// return 29;
while (true) {
var num = randomIntFromInterval(min, max);
if (num != cur) {
console.log("new", num, "cur", cur);
return num;
}
}
}
Insert cell
function randomIntFromInterval(min, max) {
// min and max included
return Math.floor(Math.random() * (max - min + 1) + min);
}
Insert cell
function randVotePerc() {
// min and max included
return Math.random();
}
Insert cell
function rowAndCol(num, width) {
var row = Math.floor(num / width);
var temp = row * width;
var column = num - temp;

// returns the coordinates for where it should be starting at [1,1]

return [column, row];
}
Insert cell
function updateRowCol(data, num) {
for (const d in data) {
data[d].row = rowsAndColsLeftToRight(data.length, d, num)[0];
data[d].col = rowsAndColsLeftToRight(data.length, d, num)[1];
data[d].color = randVotePerc();
}
var data = data.slice().sort((a, b) => d3.ascending(a.color, b.color));
for (const d in data) {
data[d].row = rowsAndColsLeftToRight(data.length, d, num)[0];
data[d].col = rowsAndColsLeftToRight(data.length, d, num)[1];
}
return data;
}
Insert cell
function updateRowColReal(data, num) {
for (const d in data) {
data[d].row = rowsAndColsLeftToRight(data.length, d, num)[0];
data[d].col = rowsAndColsLeftToRight(data.length, d, num)[1];
}
var data = data.slice().sort((a, b) => d3.ascending(a.votePerc, b.votePerc));
for (const d in data) {
data[d].row = rowsAndColsLeftToRight(data.length, d, num)[0];
data[d].col = rowsAndColsLeftToRight(data.length, d, num)[1];
}
return data;
}
Insert cell
colorScaleDemToRep = d3
.scaleLinear()
.domain([-0.5, 0.495, 0.49500001, 0.504999, 0.505, 1.5])
.range(["#0571b0", "#7ba0d9", "purple", "purple", "#f6a19a", "#cc3c28"])

//"#82a6c7", "#0571b0"
Insert cell
function createTestDists(num, seats = "435") {
var data = [];
for (let index = 0; index < seats; ++index) {
data[index] = {
id: "dem" + index,
row: rowAndCol(index, num)[0],
col: rowAndCol(index, num)[1],
won: Math.round(randVotePerc() + 0.3),
color: randVotePerc()
};
}

var data = data.slice().sort((a, b) => d3.ascending(a.color, b.color));

return data;
}
Insert cell
house = createTestDists(29, 100)
Insert cell
electionVaStateAggregate = FileAttachment("-2023-11-07-election-va-state-aggregate.json").json()
Insert cell
output = getHouseRaces(resultsKentuckyGovernor68)
Insert cell
function getHouseRaces(data) {
var temp = data["races"];

// Filter the array based on the condition
var filteredArray = temp.filter((d) => d.office == "House of Delegates");

// Log the filtered array
console.log("Filtered array:", filteredArray);

// Return the filtered array
return filteredArray;
}
Insert cell
resultsKentuckyGovernor41 = FileAttachment("results-kentucky-governor-41.json").json()
Insert cell
resultsKentuckyGovernor68 = FileAttachment("results-kentucky-governor-68.json").json()
Insert cell
function listWinners(races) {
var winners = [];
// Goes through the races and creates a new {} with all of the races in it.
for (const d in races) {
winners[races[d]["seat_number"]] = getRaceInfo(races[d]);
}
return winners;
}
Insert cell
function getRaceInfo(race) {
var output = {};
// Name of winner
//output["winnerName"] = "None";
if (race["outcome"]["won"].length != 0) {
output["winnerName"] = race["outcome"]["won"][0];
} else {
output["winnerName"] = "No Winner";
}
// Party of winner

// move the find winners name function into winnerParty
output["winnerParty"] = winnerParty(race, output["winnerName"]);
console.log("details", getWinnersVotePerc(race));
if (getWinnersVotePerc(race) != undefined) {
output["votePerc"] = getWinnersVotePerc(race);
} else {
output["votePerc"] = 1;
}

// Returns if there is a winner, the name of the winner, party of winner(Also, eventually the race percentage)
return output;
}
Insert cell
function getWinnersVotePerc(race) {
// Returns object with the vote percents of candidates
var candidateVotePerc = getCandidatesVotePerc(race);
// Get the name of the winner
var winnerName = race["outcome"]["won"][0];
var votePerc;

for (const d in candidateVotePerc) {
// Check and see if there is a winner, if so pass back the party id, if there isn't return "no winner"
if (d == winnerName) {
votePerc = candidateVotePerc[d];
}
}

return votePerc;
}
Insert cell
function getCandidatesVotePerc(race) {
var totalVotes = race["top_reporting_unit"]["total_votes"];
if (totalVotes == 0) {
return 1;
}
return getVotePerc(getRaceCandidates(race), totalVotes);
}
Insert cell
function getRaceCandidates(race) {
return race["top_reporting_unit"]["candidates"];
}
Insert cell
function getVotePerc(candidates, totalVotes) {
var votes = {};
for (const d in candidates) {
// Check and see if there is a winner, if so pass back the party id, if there isn't return "no winner"
votes[candidates[d]["nyt_id"]] =
candidates[d]["votes"]["total"] / totalVotes;
}
return votes;
}
Insert cell
function winnerParty(race, winnerName) {
var candidates = race["candidate_metadata"];
for (const d in candidates) {
// Check and see if there is a winner, if so pass back the party id, if there isn't return "no winner"
if (d == winnerName) {
return candidates[d]["party"]["nyt_id"];
}
}
return "No Winner";
}
Insert cell
data = FileAttachment("data.json").json()
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