Funnel in Vue
Using ApexCharts with Vue
This Funnel 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>Which device carries the funnel?</h1>
<p>
Pick a device. Every stage keeps its full width, faded, and the visitors
on that device are drawn solid and centred inside it, so you can read
where one device drops off faster than the other.
</p>
<div class="actions">
<span>Device</span>
<button data-device="mobile" class="active">Mobile</button>
<button data-device="desktop">Desktop</button>
<button data-device="">All (clear)</button>
</div>
<div class="chart-wrap">
<div id="chart">
<apexchart
type="funnel"
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.
//
// Visitors at each step of a signup funnel (the wholes) and how many of them
// came from one device (the parts). Each stage keeps its full width, faded,
// with the device's share solid and centred inside it.
var STEPS = ['Visited', 'Viewed pricing', 'Started trial', 'Activated', 'Subscribed']
var VISITORS = [12400, 7300, 3900, 2100, 1200]
var DEVICES = {
mobile: [7900, 3900, 1600, 700, 330],
desktop: [4500, 3400, 2300, 1400, 870]
}
function seriesFor(device) {
var s = { name: 'Visitors', data: VISITORS }
if (device) s.highlightData = DEVICES[device]
return [s]
}
function pickDevice(chart, device) {
document.querySelectorAll('[data-device]').forEach(function (b) {
b.classList.toggle('active', b.getAttribute('data-device') === device)
})
return chart.updateSeries(seriesFor(device))
}
function wireDevices(chart) {
document.querySelectorAll('[data-device]').forEach(function (b) {
b.addEventListener('click', function () {
pickDevice(chart, b.getAttribute('data-device'))
})
})
}
export default {
components: {
apexchart: VueApexCharts,
},
data: function () {
return {
series: seriesFor('mobile'),
chartOptions: {
chart: {
id: 'signup',
type: 'funnel',
height: 360,
toolbar: { show: false },
},
colors: ['#0284c7'],
plotOptions: {
bar: { barHeight: '82%', borderRadius: 0 },
},
dataLabels: {
enabled: true,
formatter: function (v, opt) {
return opt.w.globals.labels[opt.dataPointIndex] + ': ' + v.toLocaleString()
},
},
xaxis: { categories: STEPS },
legend: { show: false },
},
deviceTimer: null,
}
},
mounted: function () {
// The vue-apexcharts wrapper owns the render, so reach the live instance by
// its chart.id. (seriesFor / pickDevice live in the shared head script.)
var me = this
me.deviceTimer = window.setInterval(function () {
var chart = ApexCharts.getChartByID('signup')
if (!chart) return
window.clearInterval(me.deviceTimer)
wireDevices(chart)
}, 50)
},
beforeDestroy: function () {
window.clearInterval(this.deviceTimer)
},,
}
</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: #0284c7;
border-color: #0284c7;
}
</style>