Gauges and Radial Bars in React
Using ApexCharts with React
This Gauges and Radial 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.
//
// The company's figures are the wholes and one region's are the parts. A
// pick is an ordinary update of highlightFilter.data, one value per ring;
// null clears it.
//
// Sums: the company's figure is the regions added up, so every part fits
// inside its whole. Orders shipped this month (against a target of 12,000),
// and each team's progress toward its quarterly goal, split by the region
// that contributed it (the three regions add up to the team's figure).
var TARGET = 12000
var SHIPPED = 9300
var SHIPPED_BY_REGION = { americas: 4100, europe: 3200, apac: 2000 }
var TEAMS = ['Sales', 'Support', 'Product']
var GOALS = [72, 58, 85]
var GOALS_BY_REGION = {
americas: [32, 20, 38],
europe: [24, 22, 27],
apac: [16, 16, 20],
}
// Averages: a score or a rate over every region, which one region can beat.
// Customer satisfaction (split in lanes) and tickets resolved the same day
// (a second needle).
var SATISFACTION = 78
var SATISFACTION_BY_REGION = { americas: 84, europe: 71, apac: 66 }
var SAME_DAY = 64
var SAME_DAY_BY_REGION = { americas: 71, europe: 58, apac: 49 }
function pickRegion(region) {
document.querySelectorAll('[data-region]').forEach(function (b) {
b.classList.toggle('active', b.getAttribute('data-region') === region)
})
ApexCharts.getChartByID('shipped').updateOptions({
highlightFilter: { data: region ? [SHIPPED_BY_REGION[region]] : null },
})
ApexCharts.getChartByID('goals').updateOptions({
highlightFilter: { data: region ? GOALS_BY_REGION[region] : null },
})
ApexCharts.getChartByID('satisfaction').updateOptions({
highlightFilter: { data: region ? [SATISFACTION_BY_REGION[region]] : null },
})
ApexCharts.getChartByID('same-day').updateOptions({
highlightFilter: { data: region ? [SAME_DAY_BY_REGION[region]] : null },
})
}
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: [SHIPPED],
options: {
chart: {
id: 'shipped',
type: 'gauge',
height: 300,
toolbar: { show: false },
},
labels: ['Orders shipped'],
colors: ['#0284c7'],
plotOptions: {
radialBar: {
// A count on its own domain: 0 to the month's target.
min: 0,
max: TARGET,
dataLabels: {
value: {
fontSize: '28px',
formatter: function (v) {
return Number(v).toLocaleString()
},
},
},
},
},
tooltip: {
y: {
formatter: function (v) {
return Number(v).toLocaleString()
},
},
},
title: {
text: 'Orders shipped this month, target 12,000',
style: { fontSize: '13px' },
},
highlightFilter: { data: [SHIPPED_BY_REGION.americas] },
},
series1: GOALS,
options1: {
chart: {
id: 'goals',
type: 'radialBar',
height: 340,
toolbar: { show: false },
},
labels: TEAMS,
colors: ['#0284c7', '#f59e0b', '#e11d48'],
plotOptions: {
radialBar: {
hollow: { size: '38%' },
track: { margin: 4 },
dataLabels: {
name: { fontSize: '13px' },
value: { fontSize: '20px' },
total: {
show: true,
label: 'Average',
// While a region is picked the totals read its parts (the default
// highlightFilter.dataLabels.total: 'part'), so this averages the
// region's contributions; cleared, the teams' progress.
formatter: function (w) {
var t = w.globals.seriesTotals
return (
Math.round(
t.reduce(function (a, b) {
return a + b
}, 0) / t.length,
) + '%'
)
},
},
},
},
},
legend: { show: true, position: 'bottom' },
title: {
text: 'Quarterly goal reached, by team',
style: { fontSize: '13px' },
},
highlightFilter: { data: GOALS_BY_REGION.americas },
},
series2: [SATISFACTION],
options2: {
chart: {
id: 'satisfaction',
type: 'gauge',
height: 300,
toolbar: { show: false },
},
labels: ['Satisfaction'],
colors: ['#0f766e'],
plotOptions: {
radialBar: {
dataLabels: {
value: {
fontSize: '28px',
// The centre reads the region; while one is picked, a second line
// under it reads the whole (opts.lane is 'whole' for that line).
formatter: function (v, w, opts) {
return (
(opts && opts.lane === 'whole' ? 'company ' : '') + v + '%'
)
},
},
},
},
},
title: { text: 'Customer satisfaction', style: { fontSize: '13px' } },
highlightFilter: {
data: [SATISFACTION_BY_REGION.americas],
// Made for a part that can pass its whole: two lanes, never overlapping.
radialBar: { indicator: 'lanes' },
},
},
series3: [SAME_DAY],
options3: {
chart: {
id: 'same-day',
type: 'gauge',
height: 300,
toolbar: { show: false },
},
labels: ['Same-day resolution'],
colors: ['#ea580c'],
plotOptions: {
radialBar: {
shape: 'needle',
needle: { length: '78%' },
ticks: {
show: true,
major: { count: 6, length: 6 },
minor: { count: 1, length: 3 },
labels: { show: true, fontSize: '10px' },
},
dataLabels: { value: { fontSize: '22px' } },
},
},
title: {
text: 'Tickets resolved the same day',
style: { fontSize: '13px' },
},
highlightFilter: { data: [SAME_DAY_BY_REGION.americas] },
},
})
React.useEffect(() => {
// The react-apexcharts wrappers own the renders, so reach the live
// instances by their chart.id. (pickRegion lives in the shared head script.)
const timer = window.setInterval(() => {
if (
!ApexCharts.getChartByID('shipped') ||
!ApexCharts.getChartByID('goals') ||
!ApexCharts.getChartByID('satisfaction') ||
!ApexCharts.getChartByID('same-day')
)
return
window.clearInterval(timer)
wireRegions()
}, 50)
return () => window.clearInterval(timer)
}, [])
return (
<div>
<div className="wrap">
<h1>How does one region compare with the whole company?</h1>
<p>
Pick a region. The company's figure stays faded and the region's is
drawn solid in front. Hover any arc or needle for both values.
</p>
<div className="actions">
<span>Region</span>
<button data-region="americas" className="active">
Americas
</button>
<button data-region="europe">Europe</button>
<button data-region="apac">Asia Pacific</button>
<button data-region="">All (clear)</button>
</div>
<h2>
<small>Sums</small>The region is a share of the company
</h2>
<p>
The company's figure is the three regions added up, so a region's arc
always ends inside it. On the rings, the solid arc is the region's
contribution to each team's goal.
</p>
<div className="grid">
<div className="card">
<div id="chart">
<ReactApexChart
options={state.options}
series={state.series}
type="gauge"
height={300}
/>
</div>
</div>
<div className="card">
<div id="chart2">
<ReactApexChart
options={state.options1}
series={state.series1}
type="radialBar"
height={340}
/>
</div>
</div>
</div>
<h2>
<small>Averages</small>A region can beat the company
</h2>
<p>
A score or a rate is an average, so a region can sit above the
company. The satisfaction gauge uses <code>indicator: 'lanes'</code>{' '}
to draw the two side by side; the needle gauge fades the company's
needle.
</p>
<div className="grid">
<div className="card">
<div id="chart3">
<ReactApexChart
options={state.options2}
series={state.series2}
type="gauge"
height={300}
/>
</div>
</div>
<div className="card">
<div id="chart4">
<ReactApexChart
options={state.options3}
series={state.series3}
type="gauge"
height={300}
/>
</div>
</div>
</div>
</div>
</div>
)
}
export default ApexChart