Parliament in Vue

Using ApexCharts with Vue

This Parliament example wires ApexCharts into Vue through the vue3-apexcharts component.

Install it with npm install vue3-apexcharts apexcharts, then mount the chart with <apexchart type="..." :options="options" :series="series" />.

Vue charts guide
<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'

import 'apexcharts/unit'

// 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>