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