<template>
  <div>
    <div class="wrap">
      <h1>Population explorer</h1>
      <p>
        One dot per person, coloured by gender, the minority packed into the
        centre.
      </p>

      <div class="chart-wrap">
        <div id="chart">
          <apexchart
            type="unit"
            height="460"
            :options="chartOptions"
            :series="series"
          ></apexchart>
        </div>
      </div>

      <div class="controls">
        Minimum age: <span class="readout" id="readout">45+</span>
        <input type="range" id="age" min="45" max="90" step="1" value="45" />
      </div>
    </div>
  </div>
</template>

<script>
import VueApexCharts from 'vue-apexcharts'
import ApexCharts from 'apexcharts'

// Shared by the vanilla-js, React and Vue builds. A synthetic population model:
// as the minimum age rises, the crowd shrinks and the female share dips. Each
// dot is one person; colour is gender; the female minority nests in the centre
// of the packed blob. Dragging the slider re-buckets the data and the dots
// redistribute in place, they do not rebuild from scratch.
function countsForAge(minAge) {
  var frac = (90 - minAge) / 45 // 1 at age 45, 0 at age 90
  var total = Math.round(180 + 760 * frac)
  var femaleShare = 0.2 + 0.16 * frac
  var female = Math.round(total * femaleShare)
  return { female: female, male: total - female, total: total }
}

function readoutText(minAge) {
  var c = countsForAge(minAge)
  var pct = ((c.female / c.total) * 100).toFixed(1)
  return (
    c.total.toLocaleString() +
    ' people aged ' +
    minAge +
    '+, ' +
    c.female.toLocaleString() +
    ' female (' +
    pct +
    '%)'
  )
}

export default {
components: {
apexchart: VueApexCharts,
},
data: function () {
return {
series: [338, 602],
chartOptions: {
chart: {
  id: 'popChart',
  type: 'unit',
  height: 460,
  animations: {
    enabled: true,
    speed: 700,
  },
},
labels: ['Female', 'Male'],
colors: ['#EE5A8F', '#1AA5A0'],
legend: {
  position: 'bottom',
},
plotOptions: {
  unit: {
    layout: 'packed',
    sortByGroup: true,
    spacing: 1.1,
  },
},
},
popTimer: null,
}
},
mounted: function () {
  // Reach the live instance by its chart.id, then wire the slider.
  // (countsForAge and readoutText live in the shared head script.)
  var me = this
  me.popTimer = window.setInterval(function () {
    var chart = ApexCharts.getChartByID('popChart')
    if (!chart) return
    window.clearInterval(me.popTimer)

    var ageInput = document.querySelector('#age')
    var readout = document.querySelector('#readout')
    ageInput.addEventListener('input', function () {
      var minAge = parseInt(ageInput.value, 10)
      readout.textContent = readoutText(minAge)
      var c = countsForAge(minAge)
      chart.updateSeries([c.female, c.male])
    })
  }, 50)
},
beforeDestroy: function () {
  window.clearInterval(this.popTimer)
},,
}
</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;
}
p {
  color: #5b6b78;
  line-height: 1.5;
  margin: 0 0 20px;
}
.chart-wrap {
  background: #fff;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
}
.controls {
  margin: 20px auto 0;
  max-width: 460px;
  text-align: center;
  color: #5b6b78;
  font-size: 14px;
}
.controls input[type='range'] {
  width: 100%;
  margin-top: 8px;
}
.readout {
  font-weight: 600;
  color: #2c3e50;
}
</style>
Population Explorer - Vue Unit Charts | ApexCharts.js | ApexCharts.js