<template>
<div>
<div class="wrap">
<h1>A parliament, one election apart</h1>
<div class="controls">
<button id="btn2019" class="active" type="button">2019</button>
<button id="btn2023" type="button">2023</button>
</div>
<div class="chart-wrap">
<div id="chart">
<apexchart
type="unit"
height="440"
:options="chartOptions"
:series="series"
></apexchart>
</div>
<p class="lead">
One dot is one seat. Toggle the year and the same 315 seats recolour
in place as the chamber recomposes.
</p>
</div>
<div class="readout" id="readout"></div>
</div>
</div>
</template>
<script>
import VueApexCharts from 'vue-apexcharts'
import ApexCharts from 'apexcharts'
// Each dot is one seat. Parties sit left to right as contiguous wedges of the
// hemicycle; the same 315 seats recolour in place as the result changes, so you
// watch the majority form. Shared by the vanilla-js, React and Vue builds.
var PARTIES = ['Progressives', 'Conservatives', 'Greens', 'Liberals', 'Centre', 'Independents']
var MAJORITY = 158 // of 315 seats
var RESULTS = {
2019: [82, 78, 45, 40, 38, 32], // fragmented: no single majority
2023: [60, 162, 30, 28, 20, 15], // the Conservatives sweep to a majority
}
// Leading party + whether anyone cleared the majority line, for the readout.
function readoutFor(year) {
var seats = RESULTS[year]
var topIdx = 0
for (var i = 1; i < seats.length; i++) {
if (seats[i] > seats[topIdx]) topIdx = i
}
var top = seats[topIdx]
var verdict =
top >= MAJORITY
? '<b>' + PARTIES[topIdx] + '</b> hold an outright majority (' + top + ' of 315).'
: 'No majority: <b>' + PARTIES[topIdx] + '</b> lead with ' + top + ' of 315, short of ' + MAJORITY + '.'
return year + ': ' + verdict
}
export default {
components: {
apexchart: VueApexCharts,
},
data: function () {
return {
series: [82, 78, 45, 40, 38, 32],
chartOptions: {
chart: {
id: 'parliament',
type: 'unit',
height: 440,
animations: {
enabled: true,
speed: 800,
},
},
labels: ['Progressives', 'Conservatives', 'Greens', 'Liberals', 'Centre', 'Independents'],
colors: ['#E3000F', '#1E6FD9', '#3DA35D', '#F0A202', '#17A2B8', '#6C757D'],
legend: {
position: 'bottom',
},
plotOptions: {
unit: {
layout: 'arc',
arc: {
innerRadiusRatio: 0.35,
},
tooltip: {
// Party name + its seat total for the hovered seat.
formatter: function (o) {
return '<b>' + o.seriesName + '</b><br/>' + o.count + ' seats'
},
},
},
},
},
parliamentTimer: null,
}
},
mounted: function () {
// Reach the live instance by its chart.id, then wire the buttons.
// (RESULTS / readoutFor live in the shared head script.)
var me = this
me.parliamentTimer = window.setInterval(function () {
var chart = ApexCharts.getChartByID('parliament')
if (!chart) return
window.clearInterval(me.parliamentTimer)
var readout = document.querySelector('#readout')
var btn2019 = document.querySelector('#btn2019')
var btn2023 = document.querySelector('#btn2023')
var show = function (year, btn) {
btn2019.classList.toggle('active', btn === btn2019)
btn2023.classList.toggle('active', btn === btn2023)
readout.innerHTML = readoutFor(year)
chart.updateSeries(RESULTS[year])
}
btn2019.addEventListener('click', function () { show(2019, btn2019) })
btn2023.addEventListener('click', function () { show(2023, btn2023) })
readout.innerHTML = readoutFor(2019)
}, 50)
},
beforeDestroy: function () {
window.clearInterval(this.parliamentTimer)
},,
}
</script>
<style>
body {
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #fafbfc;
color: #2c3e50;
margin: 0;
padding: 32px 16px;
}
.wrap {
max-width: 720px;
margin: 0 auto;
}
h1 {
font-size: 22px;
margin: 0 0 8px;
}
.chart-wrap {
background: #fff;
border-radius: 8px;
padding: 16px 16px 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
}
.lead {
color: #5b6b78;
line-height: 1.5;
margin: 14px 2px 0;
font-size: 14px;
}
.controls {
margin: 8px auto 18px;
text-align: center;
}
.controls button {
font: inherit;
font-size: 14px;
cursor: pointer;
border: 1px solid #d4dbe0;
background: #fff;
color: #2c3e50;
padding: 8px 18px;
border-radius: 999px;
margin: 0 4px;
}
.controls button.active {
background: #2c3e50;
border-color: #2c3e50;
color: #fff;
}
.readout {
text-align: center;
margin-top: 14px;
font-size: 14px;
color: #5b6b78;
min-height: 20px;
}
.readout b {
color: #2c3e50;
}
</style>