diff --git a/README.md b/README.md index f8df9aa..c4de6e8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ # 2024-election-results -Userscript for the following website: - -https://www.reuters.com/graphics/USA-ELECTION/RESULTS/zjpqnemxwvx/ \ No newline at end of file +Simple page for election results for 2024 \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..d501c13 --- /dev/null +++ b/index.html @@ -0,0 +1,160 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Document</title> + <link + href="https://cdn.jsdelivr.net/npm/daisyui@4.12.14/dist/full.min.css" + rel="stylesheet" + type="text/css" + /> + <script src="https://cdn.tailwindcss.com"></script> + </head> + <body> + <div class="navbar bg-neutral text-neutral-content"> + <button class="btn btn-ghost text-xl">2024 Election Results</button> + </div> + <div class="overflow-x-auto"> + <table class="table table-xs"> + <thead> + <tr> + <th>Name</th> + <th>Party</th> + <th>Votes</th> + </tr> + </thead> + <tbody id="table-body"></tbody> + </table> + </div> + <div class="navbar bg-neutral text-neutral-content"> + <button class="btn btn-ghost text-xl">Breakdown Per State</button> + </div> + <div id="state-container" class="grid grid-cols-3 gap-4"></div> + <script> + (async () => { + const metaResp = await fetch( + "https://graphics.thomsonreuters.com/data/2024/us-elections/production/events/20241105/metadata.json" + ); + const metadata = await metaResp.json(); + const states = new Map(metadata.geo.map((c) => [c.uid, c.name])); + const parties = new Map(metadata.parties.map((c) => [c.code, c.name])); + const candidates = new Map( + metadata.candidates.map((c) => [ + c.uid, + [c.fullName, parties.get(c.candidateRaces[0].majorParty)], + ]) + ); + const presResp = await fetch( + "https://graphics.thomsonreuters.com/data/2024/us-elections/production/events/20241105/summary-votes/president.json" + ); + + const president = await presResp.json(); + let data = president[0].state_electionTypes + .map((t) => + t.state.officeRaces[0].candidateVotes.map((o) => { + return { + name: candidates + ? candidates.get(o.candidate) + : [o.candidate, "?"], + votes: o.totalVote, + }; + }) + ) + .reduce((acc, current) => { + current.forEach((candidate) => { + const existingCandidate = acc.find( + (c) => c.name === candidate.name + ); + if (existingCandidate) { + existingCandidate.votes += candidate.votes; + } else { + acc.push(candidate); + } + }); + return acc; + }, []); + + let stateVotes = president[0].state_electionTypes.map((t) => { + return { + state: states ? states.get(t.state.uid) : t.uid, + current: t.state.officeRaces[0].officeVotes[0].totalVote, + expected: t.state.officeRaces[0].officeVotes[0].totalExpectedVote, + candidates: t.state.officeRaces[0].candidateVotes.map((o) => { + return { + name: candidates + ? candidates.get(o.candidate) + : [o.candidate, "?"], + votes: o.totalVote, + }; + }), + }; + }); + + if (data.length > 0) { + let tableBody = document.getElementById("table-body"); + tableBody.innerHTML = ""; + data + .sort((a, b) => b.votes - a.votes) + .forEach((row, index) => { + let rowElement = tableBody.insertRow(); + let nameCell = rowElement.insertCell(); + let partyCell = rowElement.insertCell(); + let votesCell = rowElement.insertCell(); + nameCell.textContent = row.name[0]; + partyCell.textContent = row.name[1]; + votesCell.textContent = row.votes; + }); + } + + if (stateVotes.length > 0) { + let stateContainer = document.getElementById("state-container"); + stateContainer.innerHTML = ""; + stateVotes.forEach((row) => { + const table = document.createElement("table"); + table.setAttribute("class", `table table-xs`); + const caption = document.createElement("caption"); + caption.innerHTML = `<h2 class="text-xl">${row.state}</h2>`; + table.appendChild(caption); + + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + const headers = ["Name", "Party", "Votes"]; + headers.forEach((header) => { + const th = document.createElement("th"); + th.textContent = header; + headerRow.appendChild(th); + }); + thead.appendChild(headerRow); + table.appendChild(thead); + + const tbody = document.createElement("tbody"); + row.candidates + .sort((a, b) => b.votes - a.votes) + .forEach((candidate) => { + const rowElement = document.createElement("tr"); + + const nameCell = document.createElement("td"); + nameCell.textContent = candidate.name[0]; + rowElement.appendChild(nameCell); + + const partyCell = document.createElement("td"); + partyCell.textContent = candidate.name[1]; + rowElement.appendChild(partyCell); + + const votesCell = document.createElement("td"); + votesCell.textContent = candidate.votes; + rowElement.appendChild(votesCell); + + tbody.appendChild(rowElement); + }); + table.appendChild(tbody); + stateContainer.appendChild(table); + }); + } + })().catch((e) => { + console.error(e); + }); + </script> + </body> +</html> diff --git a/index.js b/index.js deleted file mode 100644 index 078071c..0000000 --- a/index.js +++ /dev/null @@ -1,122 +0,0 @@ -(() => { - let candidates; - let parties; - let states; - let headerContainer = document.querySelector('div.article-block:nth-child(7)'); - headerContainer.innerHTML = ''; - headerContainer.insertAdjacentHTML('afterend', `<table class="heading h3 competitive en president svelte-1gkg98x"><thead><tr><th>Name</th><th>Party</th><th>Votes</th></tr></thead><tbody id="table-body"></tbody></table><div id="state-container" style="display: grid; grid-template-columns:repeat(4, 1fr); gap: 10px;"></div>`); - const { - fetch: originalFetch - } = window; - window.fetch = async (...args) => { - let [resource, config] = args; - let response = await originalFetch(resource, config); - if (!response.ok) { - return response; - } - const f = response.url.split('/').pop(); - if (f === "metadata.json") { - const metadata = await response.json(); - states = new Map(metadata.geo.map((c) => [c.uid, c.name])); - parties = new Map(metadata.parties.map((c) => [c.code, c.name])); - candidates = new Map(metadata.candidates.map((c) => [c.uid, [c.fullName, parties.get(c.candidateRaces[0].majorParty)]])); - } else if (f === "president.json") { - - const president = await response.json(); - let data = president[0].state_electionTypes.map((t) => t.state.officeRaces[0].candidateVotes.map((o) => { - return { - "name": (candidates) ? candidates.get(o.candidate) : [o.candidate, "?"], - "votes": o.totalVote - }; - })).reduce((acc, current) => { - current.forEach((candidate) => { - const existingCandidate = acc.find((c) => c.name === candidate.name); - if (existingCandidate) { - existingCandidate.votes += candidate.votes; - } else { - acc.push(candidate); - } - }); - return acc; - }, []); - - let stateVotes = president[0].state_electionTypes.map((t) => { - return { - "state": (states) ? states.get(t.state.uid) : t.uid, - "current": t.state.officeRaces[0].officeVotes[0].totalVote, - "expected": t.state.officeRaces[0].officeVotes[0].totalExpectedVote, - "candidates": t.state.officeRaces[0].candidateVotes.map((o) => { - return { - "name": (candidates) ? candidates.get(o.candidate) : [o.candidate, "?"], - "votes": o.totalVote - }; - }) - }; - }); - - if (data.length > 0) { - let tableBody = document.getElementById('table-body'); - tableBody.innerHTML = ''; - data.sort((a, b) => b.votes - a.votes).forEach((row, index) => { - let rowElement = tableBody.insertRow(); - rowElement.setAttribute('class', `with-state-links row-${index} svelte-woitu6 first-poll-close-row`); - let nameCell = rowElement.insertCell(); - nameCell.setAttribute('class', `state en svelte-woitu6`); - let partyCell = rowElement.insertCell(); - partyCell.setAttribute('class', `state en svelte-woitu6`); - let votesCell = rowElement.insertCell(); - votesCell.setAttribute('class', `state en svelte-woitu6`); - nameCell.textContent = row.name[0]; - partyCell.textContent = row.name[1]; - votesCell.textContent = row.votes; - }); - } - - if (stateVotes.length > 0) { - let stateContainer = document.getElementById('state-container'); - stateContainer.innerHTML = ''; - stateVotes.forEach((row) => { - const table = document.createElement('table'); - const caption = document.createElement('caption'); - caption.innerHTML = ` - <h2>${row.state} (Votes Left: ${row.expected - row.current})</h2> - `; - table.appendChild(caption); - - const thead = document.createElement('thead'); - const headerRow = document.createElement('tr'); - const headers = ['Name', 'Party', 'Votes']; - headers.forEach(header => { - const th = document.createElement('th'); - th.textContent = header; - headerRow.appendChild(th); - }); - thead.appendChild(headerRow); - table.appendChild(thead); - - const tbody = document.createElement('tbody'); - row.candidates.sort((a, b) => b.votes - a.votes).forEach((candidate) => { - const rowElement = document.createElement('tr'); - - const nameCell = document.createElement('td'); - nameCell.textContent = candidate.name[0]; - rowElement.appendChild(nameCell); - - const partyCell = document.createElement('td'); - partyCell.textContent = candidate.name[1]; - rowElement.appendChild(partyCell); - - const votesCell = document.createElement('td'); - votesCell.textContent = candidate.votes; - rowElement.appendChild(votesCell); - - tbody.appendChild(rowElement); - }); - table.appendChild(tbody); - stateContainer.appendChild(table); - }); - } - } - return response; - }; -})(); \ No newline at end of file