Stacked Columns and Bars in React
Using ApexCharts with React
This Stacked Columns and Bars 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.
//
// Website sessions (thousands) per month by device (the wholes), and what
// each traffic source brought in on that device (the parts). Picking a source
// keeps every device on both charts: the stacks are drawn faded at their full
// size, with the source's own stack solid in front, from the same baseline.
var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
var DEVICES = ['Desktop', 'Mobile']
var SESSIONS = [
[42, 45, 48, 44, 50, 53],
[58, 62, 66, 71, 75, 80],
]
var SOURCES = {
search: [
[20, 22, 23, 21, 24, 25],
[24, 26, 28, 30, 31, 33],
],
social: [
[8, 9, 10, 9, 11, 12],
[22, 24, 26, 29, 31, 34],
],
email: [
[9, 9, 10, 9, 10, 11],
[7, 7, 8, 8, 9, 9],
],
}
function seriesFor(source) {
return DEVICES.map(function (name, i) {
var s = { name: name, data: SESSIONS[i] }
if (source) s.highlightData = SOURCES[source][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 pickSource(source) {
document.querySelectorAll('[data-source]').forEach(function (b) {
b.classList.toggle('active', b.getAttribute('data-source') === source)
})
return Promise.all(
['sessions', 'shares'].map(function (id) {
return ApexCharts.getChartByID(id).updateSeries(seriesFor(source))
}),
)
}
function wireSources() {
document.querySelectorAll('[data-source]').forEach(function (b) {
b.addEventListener('click', function () {
pickSource(b.getAttribute('data-source'))
})
})
}
const ApexChart = () => {
const [state, setState] = React.useState({
series: seriesFor('search'),
options: {
chart: {
id: 'sessions',
type: 'bar',
height: 360,
stacked: true,
toolbar: { show: false },
},
colors: ['#2563eb', '#16a34a'],
plotOptions: {
bar: {
columnWidth: '56%',
borderRadius: 4,
borderRadiusWhenStacked: 'last',
dataLabels: { total: { enabled: true, style: { fontSize: '12px' } } },
},
},
dataLabels: { enabled: false },
stroke: { show: true, width: 1, colors: ['#fff'] },
xaxis: { categories: MONTHS },
yaxis: { title: { text: 'Sessions (thousands)' } },
legend: { position: 'bottom' },
tooltip: { shared: true, intersect: false },
},
series1: seriesFor('search'),
options1: {
chart: {
id: 'shares',
type: 'bar',
height: 280,
stacked: true,
stackType: '100%',
toolbar: { show: false },
},
colors: ['#2563eb', '#16a34a'],
plotOptions: {
bar: { horizontal: true, barHeight: '64%' },
},
dataLabels: { enabled: true, style: { fontSize: '11px' } },
stroke: { show: true, width: 1, colors: ['#fff'] },
xaxis: { categories: MONTHS },
legend: { show: false },
tooltip: { shared: true, intersect: false },
},
})
React.useEffect(() => {
// The react-apexcharts wrapper owns the render, so reach the live instances
// by their chart.id. (seriesFor / pickSource live in the shared head script.)
const timer = window.setInterval(() => {
if (
!ApexCharts.getChartByID('sessions') ||
!ApexCharts.getChartByID('shares')
)
return
window.clearInterval(timer)
wireSources()
}, 50)
return () => window.clearInterval(timer)
}, [])
return (
<div>
<div className="wrap">
<h1>Highlight a part of every stack</h1>
<p>
Pick a traffic source. Each month's stack keeps its full size, faded,
and the sessions that source brought in form their own stack in front
of it, device by device. The total above each column states the
source's sum; the lower chart shows each device's share of the month.
</p>
<div className="actions">
<span>Source</span>
<button data-source="search" className="active">
Search
</button>
<button data-source="social">Social</button>
<button data-source="email">Email</button>
<button data-source="">All (clear)</button>
</div>
<div className="chart-wrap">
<div id="chart">
<ReactApexChart
options={state.options}
series={state.series}
type="bar"
height={360}
/>
</div>
</div>
<div className="chart-wrap">
<div id="chart2">
<ReactApexChart
options={state.options1}
series={state.series1}
type="bar"
height={280}
/>
</div>
</div>
</div>
</div>
)
}
export default ApexChart