reactjstypescriptchart.jsreact-chartjs-2

How to sort datasets for each stacked bar chart using React Chart.js 2


I am currently unable to sort the datasets that shown in each stacked bars in descending order; they are currently displaying randomly. Here is my current code

import React from 'react';
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  BarElement,
  Title,
  Tooltip,
  Legend,
} from 'chart.js';
import { Bar } from 'react-chartjs-2';
import { claimStatues, demoClaimsValue, generateMockData } from './data.mock';
import { stackedBarChartOptions } from './Barchart.options';
ChartJS.register(
  CategoryScale,
  LinearScale,
  BarElement,
  Title,
  Tooltip,
  Legend,
);

const xLabels = [
  'In-patient',
  'Out-patient',
  'Dental',
  'Vision',
  'Vaccination',
  'Total permanent disability',
  'Critical illness',
  'Accident',
  'Maternity',
  'Death',
  'Others',
];

const dataPoints = [
  {
    label: 'Received',
    data: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
    backgroundColor: '#1F659F',
  },
  {
    label: 'Rejected',
    data: [20, 30, 10, 60, 70, 60, 70, 80, 90, 100],
    backgroundColor: '#F44336',
  },
  {
    label: 'Processing',
    data: [30, 10, 20, 80, 90, 60, 70, 80, 90, 100],
    backgroundColor: '#FFC107',
  },
  {
    label: 'Approved',
    data: [40, 10, 20, 80, 90, 60, 70, 80, 90, 100],
    backgroundColor: '#4CAF50',
  },
  {
    label: 'Custom',
    data: [50, 10, 20, 80, 90, 60, 70, 80, 90, 100],
    backgroundColor: '#00BCD4',
  },
];

export const data = {
  labels: xLabels,
  datasets: dataPoints.map((dataPoint) => ({
    ...dataPoint,
  })),
};

/**
 * 
 * generateMockData({
    columns: demoClaimsValue,
    statues: claimStatues,
  }),
 */
export default function BarChart() {
  return (
    <div className="relative bg-white">
      <Bar options={stackedBarChartOptions} data={data} />
    </div>
  );
}

Here is the package json

{
  "name": "rws-econcierge-cms",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "dev:prod": "env-cmd -f .env.prod next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "export": "next build && next export",
    "build:prod": "env-cmd -f .env.prod yarn build"
  },
  "dependencies": {
    "@faker-js/faker": "^8.3.1",
    "@fullcalendar/core": "^6.1.9",
    "@fullcalendar/daygrid": "^6.1.9",
    "@fullcalendar/interaction": "^6.1.9",
    "@fullcalendar/react": "^6.1.9",
    "@heroicons/react": "^2.0.18",
    "@hookform/resolvers": "^2.9.8",
    "@tanstack/react-query": "^4.3.9",
    "@tanstack/react-query-devtools": "^4.32.6",
    "antd": "^5.8.0",
    "axios": "^0.27.2",
    "chart.js": "^4.4.1",
    "class-variance-authority": "^0.7.0",
    "classnames": "^2.3.2",
    "date-fns": "^2.29.3",
    "date-fns-tz": "^1.3.7",
    "dayjs": "^1.11.9",
    "dnd-core": "^16.0.1",
    "dompurify": "^3.0.6",
    "draft-js": "^0.11.7",
    "draftjs-to-html": "^0.9.1",
    "env-cmd": "^10.1.0",
    "file-saver": "^2.0.5",
    "immutability-helper": "^3.1.1",
    "jszip": "^3.10.1",
    "next": "12.3.0",
    "qs": "^6.11.0",
    "react": "18.2.0",
    "react-chartjs-2": "^5.2.0",
    "react-dnd": "^16.0.1",
    "react-dnd-html5-backend": "^16.0.1",
    "react-dom": "18.2.0",
    "react-draft-wysiwyg": "^1.15.0",
    "react-hook-form": "^7.35.0",
    "react-quill": "^2.0.0",
    "recoil": "^0.7.5",
    "recoil-persist": "^4.2.0",
    "underscore": "^1.13.6",
    "validator": "^13.11.0",
    "yup": "^1.2.0"
  },
  "devDependencies": {
    "@types/file-saver": "^2.0.6",
    "@types/node": "18.7.18",
    "@types/qs": "^6.9.7",
    "@types/react": "18.0.20",
    "@types/react-dom": "18.0.6",
    "@types/underscore": "^1.11.9",
    "@types/validator": "^13.11.5",
    "autoprefixer": "^10.4.11",
    "eslint": "8.23.1",
    "eslint-config-next": "12.3.0",
    "postcss": "^8.4.16",
    "prettier": "^3.0.2",
    "prettier-plugin-tailwindcss": "^0.5.3",
    "tailwindcss": "^3.1.8",
    "typescript": "4.8.3"
  }
}

I notice that the issues might be related to the data structure that i am using currently

Below is the screen short to reference

enter image description here


Solution

  • The bars are never displayed randomly; the image you attached shows that each stack has the bars in the same order, the order in which you defined datasets array: 'Received' at the bottom, then 'Approved', ..., and at the top 'Rejected' (obviously, some bars are not visible if the corresponding value is too small or zero).

    The colours follow each other in the same order in the stack; this is also the order of the items in the legend. This is the how stacked bars are expected to behave; users also expect that and can easily read such a chart. You can't change this order by a mapping of data.

    If you want to change the order of the bars as to sort them by their values, you could use floating bars; you have to disable scales.y.stacked option (default is false) and specify for each item both two numbers in an array: its base and its top.

    Here's an example of that approach with a sort of the data in your post.

    const xLabels = [
         'In-patient',
         'Out-patient',
         'Dental',
         'Vision',
         'Vaccination',
         'Total permanent disability',
         'Critical illness',
         'Accident',
         'Maternity',
         'Death',
         'Others',
    ];
    
    const dataPoints = [
         {
              label: 'Received',
              data: [20, 20, 30, 40, 50, 60, 70, 80, 90, 100],
              backgroundColor: '#1F659F',
         },
         {
              label: 'Rejected',
              data: [20, 30, 10, 60, 70, 60, 70, 80, 90, 100],
              backgroundColor: '#F44336',
         },
         {
              label: 'Processing',
              data: [30, 10, 20, 80, 90, 60, 70, 80, 90, 100],
              backgroundColor: '#FFC107',
         },
         {
              label: 'Approved',
              data: [40, 10, 20, 80, 90, 60, 70, 80, 90, 120],
              backgroundColor: '#4CAF50',
         },
         {
              label: 'Custom',
              data: [50, 10, 20, 80, 90, 60, 70, 80, 90, 100],
              backgroundColor: '#00BCD4',
         },
    ];
    
    
    const datasets = dataPoints.map((dataPoint) => ({
         ...dataPoint,
    })); // if they need to be copied
    
    datasets[0].data.forEach((_, datasetIndex)=>{ // all .data arrays should have equal length for this to work
         const all_i = datasets.map((d, dataIndex)=>[d.data[datasetIndex], dataIndex])
              .sort(([data1], [data2]) => data1 - data2);
         let sum = 0;
         all_i.forEach(
              ([data, datasetIdx]) => {
                   datasets[datasetIdx].data[datasetIndex] = [sum, sum + data];
                   sum = sum + data;
              }
         );
    });
    
    const data = {
         labels: xLabels,
         datasets,
    };
    
    const config = {
         type: 'bar',
         data: data,
         options: {
              responsive: true,
              scales:{
                   x: {
                        stacked: true,
                   },
                   // y:{
                   //    stacked: true
                   // }
              },
         },
    };
    
    new Chart(document.querySelector('#chart1'), config);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js" integrity="sha512-ZwR1/gSZM3ai6vCdI+LVF1zSq/5HznD3ZSTk7kajkaj4D292NLuduDCO1c/NT8Id+jE58KYLKT7hXnbtryGmMg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    
    
     <div style="min-height: 60vh">
        <canvas id="chart1">
        </canvas>
     </div>