Through a Forecast in React
Using ApexCharts with React
This Through a Forecast example wires ApexCharts into React through the react-apexcharts component.
Install it with npm install react-apexcharts apexcharts, then mount the chart with <Chart type="..." options={options} series={series} />.
import React from 'react'
import ReactApexChart from 'react-apexcharts'
import ApexCharts from 'apexcharts'
import './styles.css'
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'))
})
})
}
const ApexChart = () => {
const [state, setState] = React.useState({
series: seriesFor('europe'),
options: {
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'),
options1: {
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 },
},
})
React.useEffect(() => {
// The react-apexcharts wrapper owns the render, so reach the live instances
// by their chart.id. (seriesFor / pickRegion live in the shared head script.)
const timer = window.setInterval(() => {
if (
!ApexCharts.getChartByID('plans-line') ||
!ApexCharts.getChartByID('plans-stacked')
)
return
window.clearInterval(timer)
wireRegions()
}, 50)
return () => window.clearInterval(timer)
}, [])
return (
<div>
<div className="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 className="actions">
<span>Region</span>
<button data-region="europe" className="active">
Europe
</button>
<button data-region="americas">Americas</button>
<button data-region="">All (clear)</button>
</div>
<div className="chart-wrap">
<div id="chart">
<ReactApexChart
options={state.options}
series={state.series}
type="bar"
height={320}
/>
</div>
</div>
<div className="chart-wrap">
<div id="chart2">
<ReactApexChart
options={state.options1}
series={state.series1}
type="line"
height={320}
/>
</div>
</div>
</div>
</div>
)
}
export default ApexChart