React Chart.js Data Labels

Overview

This is a how-to for working with Chart.js. Chart.js is a great open source chart library downloaded over 300k times per week as of April 2022. This post will go over how to display a data label on a stacked bar chart with the chartjs-plugin-datalabels library. This plugin can be applied to a pie, donut, or any chart with a shaded area.

Source Code

Please get the source files for this demo.

GitHub Repo

Prerequisites

New React App

Create a new Next.js application

npx create-next-app@latest

Chart.JS version 3.6.0

Chart.JS and React Chart.JS a wrapper for Chart.JS.

npm i chart.js react-chartjs-2

Chart.JS Data Labels Plugin version 2.0.0

chartjs-plugin-datalabels is a plugin for Chart.JS.

npm i chartjs-plugin-datalabels

Material – (Optional) version 5.4.3

MUI – Material UI

Material is only used for the table and layout of the demo.

npm install @mui/material @mui/lab @emotion/react @emotion/styled

Data for the example

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},
];

Randomly Generated Chart Data

Create an JSON array for the chart by getting the daily counts for type 1 & 2 flights. This fictitious data has flights for Monday to Friday which is put into an array. Values for days Monday = 1, Tuesday = 2 etc.

The chart data will have {label : day, CFCount: }

  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);
  }, []);

Create the Chart Component

Under the component folder create a new file “componentsChartComp.js“.

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';
import ChartDataLabels from 'chartjs-plugin-datalabels';

Register the Chart.JS and Data Label Components

Remember, every single component from chart.js import your chart will use must be registered.

ChartJS.register(
  CategoryScale,
  LinearScale,
  BarElement,
  ChartDataLabels,
  Title,
  Tooltip,
  Legend
);

Enable the Data Label Plugin

To enable a stacked bar chart, set stacked to true under options -> scales -> x & y.
The data labels must be set in two areas, the options and dataset

Set the options -> plugins -> dataLabels: { display: true }” and then dataset -> dataLabel -> color. In this example the color is white for max contrast.

export default function  ChartComp(props) {

  const options = {
    plugins: {
      datalabels: {
        display: true,
      },
      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',
        datalabels: {
          color: 'white'
        },
      },
      {
        label: 'General Aviation',
        data: props.data.map(c => c.generalCount),
        backgroundColor: '#ff6361',
        datalabels: {
          color: 'white'
        },
      },
    ],
    tooltips: {
  
    },
  };

Render the Bar Chart

  return <Bar options={options} data={data} />

How to Generate Chart Data from Raw Data

From my experience building charts for custom web applications, the raw API data must be modified to work with a chart. In the example below, each day will count Type 1 or 2.


Loop the raw data to create a JSON array by getting the daily counts for Type 1 & 2 flights using the JavaScript filter() function. 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.

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 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([])  
  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);
  }, []);

  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={2}></Grid>
          <Grid item xs={12} md={8}>            
              <ChartComp  data={chartData}                           
                          onHandleBarClickEvent={onHandleBarClickEvent}/>
            
          </Grid>
        </Grid>
      </Box>            
    </div>
  );
}