React Chart.js Stacked Bar Chart Data Table Click Event
Table of Contents
Overview
This post will go over how to display a stacked bar chart that changes the data in a table. Basically, drilling down and show the detailed data behind an individual stacked bar on the chart.
Source Code
Please get the source files for this demo.
GitHub RepoPrerequisites
Chart.JS version 3.6.0
Chart.JS and React Chart.JS a wrapper for Chart.JS. This is a great open source chart library that is downloaded over 300k times per week as of March 2022
npm i chart.js react-chartjs-2
Material – (Optional)
MUI – Material UI v5.4.3
Material is only used for the table and layout of the demo.
npm install @mui/material @mui/lab @emotion/react @emotion/styled
Raw Data for the Chart and Table
Raw data for the Chart Component. You can put this in a separate file and export it or just drop it in the Parent Component App.js.
const rawData = [
{id: 1, day: 1, type: 1, airline: 'Southwest', passengers: 162, parkedHours: 24},
{id: 2, day: 1, type: 1, airline: 'Southwest', passengers: 208, parkedHours: 8},
{id: 3, day: 2, type: 1, airline: 'Southwest', passengers: 176, parkedHours: 16},
{id: 4, day: 2, type: 1, airline: 'Southwest', passengers: 204, parkedHours: 24},
{id: 5, day: 3, type: 1, airline: 'Southwest', passengers: 158, parkedHours: 58},
{id: 6, day: 4, type: 1, airline: 'Southwest', passengers: 202, parkedHours: 22},
{id: 7, day: 0, type: 1, airline: 'Southwest', passengers: 218, parkedHours: 38},
{id: 8, day: 4, type: 1, airline: 'Southwest', passengers: 136, parkedHours: 6},
{id: 9, day: 3, type: 1, airline: 'Southwest', passengers: 124, parkedHours: 24},
{id: 10, day: 3, type: 1, airline: 'Southwest', passengers: 158, parkedHours: 2},
{id: 11, day: 1, type: 1, airline: 'American', passengers: 162, parkedHours: 24},
{id: 12, day: 1, type: 1, airline: 'American', passengers: 208, parkedHours: 8},
{id: 13, day: 2, type: 1, airline: 'American', passengers: 176, parkedHours: 16},
{id: 14, day: 2, type: 1, airline: 'American', passengers: 204, parkedHours: 24},
{id: 15, day: 3, type: 1, airline: 'American', passengers: 158, parkedHours: 58},
{id: 16, day: 4, type: 1, airline: 'American', passengers: 202, parkedHours: 22},
{id: 17, day: 0, type: 1, airline: 'American', passengers: 218, parkedHours: 38},
{id: 18, day: 4, type: 1, airline: 'American', passengers: 136, parkedHours: 6},
{id: 19, day: 3, type: 1, airline: 'American', passengers: 124, parkedHours: 24},
{id: 20, day: 3, type: 1, airline: 'American', passengers: 158, parkedHours: 2},
{id: 21, day: 1, type: 1, airline: 'Allegiant', passengers: 162, parkedHours: 24},
{id: 22, day: 1, type: 1, airline: 'Allegiant', passengers: 208, parkedHours: 8},
{id: 23, day: 2, type: 1, airline: 'Allegiant', passengers: 176, parkedHours: 16},
{id: 24, day: 2, type: 1, airline: 'Allegiant', passengers: 204, parkedHours: 24},
{id: 25, day: 3, type: 1, airline: 'Allegiant', passengers: 158, parkedHours: 58},
{id: 26, day: 4, type: 1, airline: 'Allegiant', passengers: 202, parkedHours: 22},
{id: 27, day: 0, type: 1, airline: 'Allegiant', passengers: 218, parkedHours: 38},
{id: 28, day: 4, type: 1, airline: 'Allegiant', passengers: 136, parkedHours: 6},
{id: 29, day: 3, type: 1, airline: 'Allegiant', passengers: 124, parkedHours: 24},
{id: 30, day: 3, type: 1, airline: 'Allegiant', passengers: 158, parkedHours: 2},
{id: 31, day: 1, type: 2, airline: 'Capital One', passengers: 12, parkedHours: 4},
{id: 32, day: 1, type: 2, airline: 'Bank of America', passengers: 28, parkedHours: 8},
{id: 33, day: 2, type: 2, airline: 'Charles Schwab', passengers: 16, parkedHours: 6},
{id: 34, day: 2, type: 2, airline: 'Iron Maiden Corp', passengers: 24, parkedHours: 24},
{id: 35, day: 3, type: 2, airline: 'Scorpions Corp', passengers: 18, parkedHours: 8},
{id: 36, day: 4, type: 2, airline: 'Fed Ex', passengers: 22, parkedHours: 2},
{id: 37, day: 0, type: 2, airline: 'Amazon', passengers: 8, parkedHours: 8},
{id: 38, day: 4, type: 2, airline: 'M Hotel', passengers: 16, parkedHours: 6},
{id: 39, day: 3, type: 2, airline: 'Wells Corp', passengers: 4, parkedHours: 24},
{id: 40, day: 3, type: 2, airline: 'Smith Corp', passengers: 18, parkedHours: 2},
{id: 41, day: 3, type: 2, airline: 'Walton Corp', passengers: 8, parkedHours: 5},
];
Short Answer – How to Enable the Click Event
From the chart component, import useRef hook from ‘react’
import React, { useRef } from 'react';
import {
Bar,
getElementAtEvent,
} from 'react-chartjs-2';
Create a Reference to the Chart
Create an instance of the a reference and add it in the chart component.
export default function ChartComp(props) {
const chartRef = useRef();
...
return <Bar options={options} data={data} onClick={onClick} ref={chartRef}/>;
How to Get the Index and Stack Bar Location.
Passing the reference chartRef and event to getElementAtEvent from ‘react-chartjs-2’, we can extract the index and dataSetIndex. The index will have the label (1 to 5) value and the dataSetIndex is the location of the stacked bar (0 for bottom, 1 for top in this case).
const onClick = (event) => {
const elem = getElementAtEvent(chartRef.current, event)
props.onHandleBarClickEvent(elem[0].index, elem[0].datasetIndex)
}
The Long Answer

Create the Chart Component
Under the component folder create a new file “components\ChartComp.js“.
Import Libraries
Add the following imports.
import { useState, useEffect } from 'react';
import React, { useRef } from 'react';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import {
Bar,
getElementAtEvent,
} from 'react-chartjs-2';
Register the Chart.JS Components
Remember, every single component from chart.js import your chart will use must be registered.
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
Chart Options and Data
To enable a stacked bar chart, set stacked to true under options -> scales -> x & y.
const options = {
plugins: {
title: {
display: true,
text: '- Stacked',
},
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true,
},
},
};
const labels= props.data.map(c => c.label);
const data = {
labels,
datasets: [
{
label: 'Commercial Flights',
data: props.data.map(c => c.commercialCount),
backgroundColor: '#58508d',
},
{
label: 'General Aviation',
data: props.data.map(c => c.generalCount),
backgroundColor: '#ff6361',
},
],
tooltips: {
},
};
The Entire Chart Component – All Together
All together
import React, { useRef } from 'react';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import {
Bar,
getElementAtEvent,
} from 'react-chartjs-2';
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
export default function ChartCompBak(props) {
const chartRef = useRef();
const options = {
plugins: {
title: {
display: true,
text: '- Stacked',
},
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true,
},
},
};
const labels= props.data.map(c => c.label);
const data = {
labels,
datasets: [
{
label: 'Commercial Flights',
data: props.data.map(c => c.commercialCount),
backgroundColor: '#58508d',
},
{
label: 'General Aviation',
data: props.data.map(c => c.generalCount),
backgroundColor: '#ff6361',
},
],
tooltips: {
},
};
const onClick = (event) => {
const elem = getElementAtEvent(chartRef.current, event)
props.onHandleBarClickEvent(elem[0].index, elem[0].datasetIndex)
}
return <Bar options={options} data={data} onClick={onClick} ref={chartRef}/>;
}
Data Table Component
Under the component folder create a new file “components\MatDataTable.js“.
Copy and paste the code below
import { useEffect, useState } from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
export default function MatDataTable(props) {
const [ tableData, setTableData ] = useState(props.data)
useEffect(() => {
setTableData(props.data)
}, [props.data]);
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 350 }} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>Airline</TableCell>
<TableCell align="right">Hours Parked</TableCell>
<TableCell align="right">Passengers</TableCell>
</TableRow>
</TableHead>
<TableBody>
{tableData.map((row, index) => (
<TableRow
key={index}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.airline}
</TableCell>
<TableCell align="right">{row.parkedHours}</TableCell>
<TableCell align="right">{row.passengers}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
The Parent App.js Component
The App.js component converts the raw data to chart data as explained above. Then renders the ChartComp and MatDataTable components. MatDataTable will initially be empty or hidden. When a chart bar is click then the parent will modify the table data that populates the MatDataTable component
How to Generate Chart Data from Raw Data
From my experience building charts for custom web applications, the raw API data must be converted/massaged to work with a chart.
Loop the raw data to create a JSON array by getting the daily counts for type 1 & 2 flights. This fake data has flights for Monday to Friday using values 1 to 5 from an array. Values for days Monday = 1, Tuesday = 2 etc.
Use the JavaScript filter to get the count for each type on each day.
rawData.filter(c => c.day === i && c.type === 1).length
The chart data will have {label : day[i], commercialCount: cfCnt, generalCount: gaCnt: } pushed into an array.
const [ chartData, setChartData ] = useState([])
const [ isLoaded, setIsLoaded ] = useState(false);
...
useEffect(() => {
setIsLoaded(false)
const day = ['Mon', 'Tues', 'Wed', 'Thu', 'Fri']
var chartData = []
for(let i=0; i <= 5; i++) {
const cfCnt = rawData.filter(c => c.day === i && c.type === 1).length
const gaCnt = rawData.filter(c => c.day === i && c.type === 2).length
chartData.push({label: day[i], commercialCount: cfCnt, generalCount: gaCnt})
}
setChartData(chartData)
setTimeout(() => {
setIsLoaded(true)
}, 100);
}, []);
The whole App.js component.
import { useState, useEffect } from 'react';
import ChartComp from './components/ChartComp'
import MatDataTable from './components/MatDataTable';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
const rawData = [
{id: 1, day: 1, type: 1, airline: 'Southwest', passengers: 162, parkedHours: 24},
{id: 2, day: 1, type: 1, airline: 'Southwest', passengers: 208, parkedHours: 8},
{id: 3, day: 2, type: 1, airline: 'Southwest', passengers: 176, parkedHours: 16},
{id: 4, day: 2, type: 1, airline: 'Southwest', passengers: 204, parkedHours: 24},
{id: 5, day: 3, type: 1, airline: 'Southwest', passengers: 158, parkedHours: 58},
{id: 6, day: 4, type: 1, airline: 'Southwest', passengers: 202, parkedHours: 22},
{id: 7, day: 0, type: 1, airline: 'Southwest', passengers: 218, parkedHours: 38},
{id: 8, day: 4, type: 1, airline: 'Southwest', passengers: 136, parkedHours: 6},
{id: 9, day: 3, type: 1, airline: 'Southwest', passengers: 124, parkedHours: 24},
{id: 10, day: 3, type: 1, airline: 'Southwest', passengers: 158, parkedHours: 2},
{id: 11, day: 1, type: 1, airline: 'American', passengers: 162, parkedHours: 24},
{id: 12, day: 1, type: 1, airline: 'American', passengers: 208, parkedHours: 8},
{id: 13, day: 2, type: 1, airline: 'American', passengers: 176, parkedHours: 16},
{id: 14, day: 2, type: 1, airline: 'American', passengers: 204, parkedHours: 24},
{id: 15, day: 3, type: 1, airline: 'American', passengers: 158, parkedHours: 58},
{id: 16, day: 4, type: 1, airline: 'American', passengers: 202, parkedHours: 22},
{id: 17, day: 0, type: 1, airline: 'American', passengers: 218, parkedHours: 38},
{id: 18, day: 4, type: 1, airline: 'American', passengers: 136, parkedHours: 6},
{id: 19, day: 3, type: 1, airline: 'American', passengers: 124, parkedHours: 24},
{id: 20, day: 3, type: 1, airline: 'American', passengers: 158, parkedHours: 2},
{id: 21, day: 1, type: 1, airline: 'Allegiant', passengers: 162, parkedHours: 24},
{id: 22, day: 1, type: 1, airline: 'Allegiant', passengers: 208, parkedHours: 8},
{id: 23, day: 2, type: 1, airline: 'Allegiant', passengers: 176, parkedHours: 16},
{id: 24, day: 2, type: 1, airline: 'Allegiant', passengers: 204, parkedHours: 24},
{id: 25, day: 3, type: 1, airline: 'Allegiant', passengers: 158, parkedHours: 58},
{id: 26, day: 4, type: 1, airline: 'Allegiant', passengers: 202, parkedHours: 22},
{id: 27, day: 0, type: 1, airline: 'Allegiant', passengers: 218, parkedHours: 38},
{id: 28, day: 4, type: 1, airline: 'Allegiant', passengers: 136, parkedHours: 6},
{id: 29, day: 3, type: 1, airline: 'Allegiant', passengers: 124, parkedHours: 24},
{id: 30, day: 3, type: 1, airline: 'Allegiant', passengers: 158, parkedHours: 2},
{id: 31, day: 1, type: 2, airline: 'Capital One', passengers: 12, parkedHours: 4},
{id: 32, day: 1, type: 2, airline: 'Bank of America', passengers: 28, parkedHours: 8},
{id: 33, day: 2, type: 2, airline: 'Charles Schwab', passengers: 16, parkedHours: 6},
{id: 34, day: 2, type: 2, airline: 'Iron Maiden Corp', passengers: 24, parkedHours: 24},
{id: 35, day: 3, type: 2, airline: 'Scorpions Corp', passengers: 18, parkedHours: 8},
{id: 36, day: 4, type: 2, airline: 'Fed Ex', passengers: 22, parkedHours: 2},
{id: 37, day: 0, type: 2, airline: 'Amazon', passengers: 8, parkedHours: 8},
{id: 38, day: 4, type: 2, airline: 'M Hotel', passengers: 16, parkedHours: 6},
{id: 39, day: 3, type: 2, airline: 'Wells Corp', passengers: 4, parkedHours: 24},
{id: 40, day: 3, type: 2, airline: 'Smith Corp', passengers: 18, parkedHours: 2},
{id: 41, day: 3, type: 2, airline: 'Walton Corp', passengers: 8, parkedHours: 5},
];
export default function App() {
const [ chartData, setChartData ] = useState([])
const [ tableData, setTableData ] = useState([])
useEffect(() => {
const day = ['Mon', 'Tues', 'Wed', 'Thu', 'Fri']
var chartData = []
for(let i=0; i <= 5; i++) {
const cfCnt = rawData.filter(c => c.day === i && c.type === 1).length
const gaCnt = rawData.filter(c => c.day === i && c.type === 2).length
chartData.push({label: day[i], commercialCount: cfCnt, generalCount: gaCnt})
}
setChartData(chartData)
}, []);
const onHandleBarClickEvent = (barIndex, stackIndex) => {
if(stackIndex === 0) {
setTableData(rawData.filter(c => c.day === barIndex && c.type === 1))
}
else {
setTableData(rawData.filter(c => c.day === barIndex && c.type === 2))
}
}
return (
<div className="App">
<Box sx={{ flexGrow: 1, marginTop: 12 }}>
<Grid container spacing={2}>
<Grid item xs={12} md={1}></Grid>
<Grid item xs={6} md={6}>
<ChartComp data={chartData}
onHandleBarClickEvent={onHandleBarClickEvent}/>
</Grid>
<Grid item xs={6} md={4}>
<MatDataTable data={tableData}/>
</Grid>
</Grid>
</Box>
</div>
);
}
You must be logged in to post a comment.