commit 941d0600045b324ec1029d4b62e6f78dcccaaedf Author: Christian Lawson-Perfect Date: Sun Feb 9 20:32:43 2025 +0000 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bc0f76 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.make.* \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..9817f03 --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + Big Math-Off 2024 voting results + + + + +
+

Big Math-Off 2024 voting results

+
+
+ + + +
+ + \ No newline at end of file diff --git a/poll.css b/poll.css new file mode 100644 index 0000000..9512f25 --- /dev/null +++ b/poll.css @@ -0,0 +1,4 @@ +h2 { + margin: 0 0 0.5rem 0; + text-align: center; +} \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..140e36a --- /dev/null +++ b/script.js @@ -0,0 +1,151 @@ +import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm" + +import * as Plot from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm"; + +function clean_poll_data(data, rel_start_time) { + const {start_date, end_date, question, answers, votes} = data; + + const num_votes = votes.length; + + const totals = answers.map(a => 0); + totals.push(0); + + const start_time = new Date(start_date); + const end_time = new Date(end_date); + + answers.forEach((a,i) => { + votes.splice(0,0,{time: start_time, answer: i}); + }) + + votes.forEach((v,i) => { + v.time = new Date(v.time); + v.total = totals[v.answer]; + v.answer_name = answers[v.answer].answer; + totals[v.answer] += 1; + }); + + const last_vote_time = votes[votes.length-1].time; + const duration = last_vote_time - start_time; + + answers.forEach((a,i) => { + votes.push({time: last_vote_time, answer: i, total: totals[i]-1, answer_name: answers[i].answer}); + }) + + votes.forEach(v => {v.time = v.time - start_time + rel_start_time}); + + return {start_date, end_date, duration, question, answers, votes, last_vote_time}; + +} + +async function plot_poll(ids, container) { + const reqs = await Promise.all(ids.map(id => fetch(`https://aperiodical.com/wp-json/wp-polls/v3/results/${id}`))); + const datas = await Promise.all(reqs.map(r => r.json())); + console.log(datas); + let all_votes = []; + const rel_start_time = 60*60*8 * 1000; + let max_duration = 0; + let title = ''; + let num_answers = 0; + for(let data of datas) { + const {votes, answers, question, duration, last_vote_time} = clean_poll_data(data, rel_start_time); + votes.forEach(v => v.answer += num_answers); + title = question; + all_votes = all_votes.concat(votes); + max_duration = Math.max(duration, max_duration); + num_answers += answers.length; + } + console.table(all_votes); + + container.innerHTML = ''; + + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = 'poll.css'; + container.append(link); + + const header = document.createElement('h2'); + header.textContent = title; + container.append(header); + + const plot = Plot.plot({ + y: { + label: 'Votes', + grid: true, + }, + x: { + label: 'Time', + tickFormat: t => { + t /= 1000; + const seconds = t % 60; + t = (t-seconds)/60; + const minutes = t % 60; + t = (t-minutes)/60; + const hours = t % 24; + return `${hours.toString().padStart(2,'0')}:${minutes.toString().padStart(2,'0')}`; + } + }, + marks: [ + Plot.line([{time: rel_start_time, total: 0}, {time: max_duration + rel_start_time, total: 0}],{x:'time', y:'total'}), + Plot.lineY(all_votes, {x: "time", y: "total", z: "answer", stroke: 'answer_name'}), + Plot.text(all_votes, Plot.selectLast({x:'time', y: 'total', z: 'answer', text: 'answer_name', textAnchor: 'end', dx: -6})) + ] + }) + container.append(plot); +} + +class PollPlotElement extends HTMLElement { + constructor() { + super(); + this.attachShadow({mode:'open'}); + } + + static observedAttributes = ['pollids']; + + attributeChangedCallback() { + this.make_plot(); + } + + make_plot() { + const poll_ids = this.getAttribute('pollids').split(',').map(n=>parseInt(n)); + if(poll_ids.some(id=>isNaN(id))) { + return; + } + plot_poll(poll_ids, this.shadowRoot); + } +} + +customElements.define('poll-plot', PollPlotElement); + +(async () => { + const polls = await (await fetch('https://aperiodical.com/wp-json/wp-polls/v3/polls')).json(); + window.polls = polls; + polls.sort((a,b) => (new Date(b.opens)) - (new Date(a.opens))); + const params = new URLSearchParams(location.search); + let pollids, year; + if(params.has('year')) { + year = params.get('year'); + console.log(year); + pollids = polls.filter(p => (new Date(p.opens)).getFullYear()==year).map(p=>p.id); + } else { + if(params.has('pollid')) { + pollids = params.get('pollid'); + } else { + pollids = polls[0].id; + } + } + document.querySelector('poll-plot').setAttribute('pollids',pollids); + + const polls_list = document.getElementById('other-matches'); + for(let p of polls) { + const li = document.createElement('li'); + const a = document.createElement('a'); + a.href = `?pollid=${p.id}`; + if(pollids.includes(p.id)) { + a.setAttribute('aria-current', 'page'); + } + const opens = new Date(p.opens); + a.textContent = `${opens.getFullYear()} - ${p.question}`; + li.append(a); + polls_list.append(li); + } +})(); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..0b77c99 --- /dev/null +++ b/style.css @@ -0,0 +1,12 @@ +body { + font-family: sans-serif; +} + +poll-plot { + padding: 1em; + display: inline-block; +} + +a[aria-current="page"] { + font-weight: bold; +} \ No newline at end of file