Through a Forecast in Vue
Using ApexCharts with Vue
This Through a Forecast 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>Highlight a part through a forecast</h1>
<p>
Pick a region. Each plan's subscriptions stay on the charts at their
full value, faded in the stacked columns and dashed on the step line,
and the region's share is drawn solid. The last three months are a
forecast: the part keeps the forecast's dashes and lighter fill there
too.
</p>
<div class="actions">
<span>Region</span>
<button data-region="europe" class="active">Europe</button>
<button data-region="americas">Americas</button>
<button data-region="">All (clear)</button>
</div>
<div class="chart-wrap">
<div id="chart">
<apexchart
type="bar"
height="320"
:options="chartOptions"
:series="series"
></apexchart>
</div>
</div>
<div class="chart-wrap">
<div id="chart2">
<apexchart
type="line"
height="320"
:options="chartOptions1"
:series="series1"
></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.
//
// New subscriptions per month by plan (the wholes), and the ones sold in one
// region (the parts). The last three months are a forecast. Picking a region
// keeps every plan on both charts: the whole is dashed or faded, the region's
// share is solid, and past the forecast cut both keep the forecast look.
var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep']
var PLANS = ['Team', 'Personal']
var SUBSCRIPTIONS = [
[120, 132, 128, 145, 150, 162, 170, 178, 186],
[80, 84, 90, 88, 96, 101, 104, 110, 114]
]
var REGIONS = {
europe: [
[52, 58, 55, 63, 66, 71, 74, 78, 81],
[30, 33, 35, 34, 38, 40, 41, 43, 45]
],
americas: [
[48, 51, 50, 57, 58, 63, 66, 69, 72],
[36, 37, 40, 39, 42, 44, 46, 49, 50]
]
}
function seriesFor(region) {
return PLANS.map(function (name, i) {
var s = { name: name, data: SUBSCRIPTIONS[i] }
if (region) s.highlightData = REGIONS[region][i]
return s
})
}
// One call per chart and pick: the parts go in, each chart animates from what
// is on screen. Clearing is the same call without them.
function pickRegion(region) {
document.querySelectorAll('[data-region]').forEach(function (b) {
b.classList.toggle('active', b.getAttribute('data-region') === region)
})
return Promise.all(
['plans-stacked', 'plans-line'].map(function (id) {
return ApexCharts.getChartByID(id).updateSeries(seriesFor(region))
})
)
}
function wireRegions() {
document.querySelectorAll('[data-region]').forEach(function (b) {
b.addEventListener('click', function () {
pickRegion(b.getAttribute('data-region'))
})
})
}
export default {
components: {
apexchart: VueApexCharts,
},
data: function () {
return {
series: seriesFor('europe'),
chartOptions: {
chart: {
id: 'plans-stacked',
type: 'bar',
height: 320,
stacked: true,
toolbar: { show: false },
},
colors: ['#0f766e', '#d97706'],
plotOptions: {
bar: { columnWidth: '55%', borderRadius: 4, borderRadiusApplication: 'end' },
},
forecastDataPoints: { count: 3, fillOpacity: 0.5, dashArray: 4, strokeWidth: 1 },
dataLabels: { enabled: false },
xaxis: { categories: MONTHS },
yaxis: { title: { text: 'New subscriptions' } },
legend: { position: 'bottom' },
tooltip: { shared: true, intersect: false },
},
series1: seriesFor('europe'),
chartOptions1: {
chart: {
id: 'plans-line',
type: 'line',
height: 320,
toolbar: { show: false },
},
colors: ['#0f766e', '#d97706'],
stroke: { curve: 'stepline', width: 3 },
forecastDataPoints: { count: 3, dashArray: 6 },
dataLabels: { enabled: false },
xaxis: { categories: MONTHS },
yaxis: { min: 0, title: { text: 'New subscriptions' } },
legend: { position: 'bottom' },
tooltip: { shared: true, intersect: false },
},
regionTimer: null,
}
},
mounted: function () {
// The vue-apexcharts wrapper owns the render, so reach the live instances by
// their chart.id. (seriesFor / pickRegion live in the shared head script.)
var me = this
me.regionTimer = window.setInterval(function () {
if (!ApexCharts.getChartByID('plans-line') || !ApexCharts.getChartByID('plans-stacked')) return
window.clearInterval(me.regionTimer)
wireRegions()
}, 50)
},
beforeDestroy: function () {
window.clearInterval(this.regionTimer)
},,
}
</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);
margin: 0 0 16px;
}
.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: #0f766e;
border-color: #0f766e;
}
</style>