Averages Past the Whole in Vue

Using ApexCharts with Vue

This Averages Past the Whole 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>Averages can outgrow the whole</h1>
      <p>
        Each bar is a store's average profit per order across every product.
        Pick a product line to draw that line's own average solid in front.
        Where it beats the overall average, the axis stretches to fit it and a
        dashed edge marks where the overall average ends; where it loses money,
        it is drawn on its own side of zero.
      </p>
      <div class="actions">
        <span>Product line</span>
        <button data-line="premium" class="active">Premium</button>
        <button data-line="budget">Budget</button>
        <button data-line="">All (clear)</button>
      </div>
      <div class="chart-wrap">
        <div id="chart">
          <apexchart
            type="bar"
            height="360"
            :options="chartOptions"
            :series="series"
          ></apexchart>
        </div>
      </div>
    </div>
  </div>
</template>

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

import 'apexcharts/features/highlight-filter'

// Shared by the vanilla-js, React and Vue builds.

function money(v) {
  return (v < 0 ? '-$' : '$') + Math.abs(Math.round(v))
}
//
// Average profit per order by store, across all products (the wholes), and the
// same average over one product line (the parts). An average over a subset
// can be larger than the overall one, or fall on the other side of zero, so
// the parts here do both.
var STORES = ['Seattle', 'Denver', 'Austin', 'Chicago', 'Boston', 'Miami']
var ALL = [42, -18, 31, 24, 12, -9]
var LINES = {
  premium: [{ x: 'Seattle', y: 42, highlight: 61 }, { x: 'Denver', y: -18, highlight: -4 }, { x: 'Austin', y: 31, highlight: 48 }, { x: 'Chicago', y: 24, highlight: 19 }, { x: 'Boston', y: 12, highlight: 27 }, { x: 'Miami', y: -9, highlight: 6 }],
  budget: [{ x: 'Seattle', y: 42, highlight: 18 }, { x: 'Denver', y: -18, highlight: -26 }, { x: 'Austin', y: 31, highlight: 12 }, { x: 'Chicago', y: 24, highlight: 28 }, { x: 'Boston', y: 12, highlight: -3 }, { x: 'Miami', y: -9, highlight: -21 }]
}

function seriesFor(line) {
  return [{
    name: 'Avg profit per order',
    data: line
      ? LINES[line]
      : STORES.map(function (s, i) { return { x: s, y: ALL[i] } })
  }]
}

function pickLine(chart, line) {
  document.querySelectorAll('[data-line]').forEach(function (b) {
    b.classList.toggle('active', b.getAttribute('data-line') === line)
  })
  return chart.updateSeries(seriesFor(line))
}

function wireLines(chart) {
  document.querySelectorAll('[data-line]').forEach(function (b) {
    b.addEventListener('click', function () {
      pickLine(chart, b.getAttribute('data-line'))
    })
  })
}

export default {
components: {
apexchart: VueApexCharts,
},
data: function () {
return {
series: seriesFor('premium'),
chartOptions: {
chart: {
  id: 'profit',
  type: 'bar',
  height: 360,
  toolbar: { show: false },
},
colors: ['#0d9488'],
plotOptions: {
  bar: { horizontal: true, barHeight: '62%', borderRadius: 4 },
},
dataLabels: {
  enabled: true,
  formatter: money,
},
xaxis: {
  labels: { formatter: money },
},
grid: { xaxis: { lines: { show: true } }, yaxis: { lines: { show: false } } },
tooltip: {
  y: { formatter: money },
},
},
lineTimer: null,
}
},
mounted: function () {
  // The vue-apexcharts wrapper owns the render, so reach the live instance by
  // its chart.id. (seriesFor / pickLine live in the shared head script.)
  var me = this
  me.lineTimer = window.setInterval(function () {
    var chart = ApexCharts.getChartByID('profit')
    if (!chart) return
    window.clearInterval(me.lineTimer)
    wireLines(chart)
  }, 50)
},
beforeDestroy: function () {
  window.clearInterval(this.lineTimer)
},,
}
</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);
}
.actions {
  margin: 0 0 14px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.actions span {
  font-size: 13px;
  color: #5b6b78;
  margin-right: 4px;
}
.actions button {
  color: #2c3e50;
  background: #fff;
  border: 1px solid #cfd8e3;
  padding: 6px 14px;
  font-weight: 600;
  font-size: 13px;
  border-radius: 6px;
  cursor: pointer;
}
.actions button.active {
  color: #fff;
  background: #0d9488;
  border-color: #0d9488;
}
</style>