Skip to content
Snippets Groups Projects
StaffGraph.tsx 5.79 KiB
import { useContext } from 'react';
import { Bar } from 'react-chartjs-2';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';

import { NrenStaff } from "compendium/Schema";
import { createNRENStaffDataset } from "compendium/helpers/dataconversion";
import DataPage from 'compendium/components/DataPage';
import Filter from "compendium/components/graphing/Filter"
import { Sections } from "compendium/helpers/constants";
import WithLegend from 'compendium/components/WithLegend';
import htmlLegendPlugin from 'compendium/plugins/HTMLLegendPlugin';
import { FilterSelectionContext } from 'compendium/providers/FilterSelectionProvider';
import { useData } from 'compendium/helpers/useData';
import titles from "compendium/titles";

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

const chartOptions = {
    maintainAspectRatio: false,
    animation: {
        duration: 0
    },
    plugins: {
        htmlLegend: {
            // ID of the container(s) to put the legend in
            containerIDs: ['legendtop', 'legendbottom'],
        },
        legend: {
            display: false,
        },
        tooltip: {
            callbacks: {
                label: function (tooltipItem) {
                    let label = tooltipItem.dataset.label || '';

                    if (tooltipItem.parsed.x !== null) {
                        label += `: ${tooltipItem.parsed.x}%`
                    }
                    return label;
                }
            },

        },
    },
    scales: {
        x: {
            position: 'top' as const,
            stacked: true,
            ticks: {
                callback: (value: string | number, index: number) => {
                    return `${index * 10}%`;
                },
            },
        },
        x2: {
            ticks: {
                callback: (value: string | number) => {
                    if (typeof value === 'number') {
                        return `${value}%`;
                    }
                    return value;
                },
            },
            grid: {
                drawOnChartArea: false
            },
            afterDataLimits: function (axis) {
                const indices = Object.keys(ChartJS.instances)

                // initial values should be far outside possible range
                let max = -999999
                let min = 999999

                for (const index of indices) {
                    if (ChartJS.instances[index] && axis.chart.scales.x2) {
                        min = Math.min(ChartJS.instances[index].scales.x.min, min);
                        max = Math.max(ChartJS.instances[index].scales.x.max, max);
                    }
                }

                axis.chart.scales.x2.options.min = min;
                axis.chart.scales.x2.options.max = max;
                axis.chart.scales.x2.min = min;
                axis.chart.scales.x2.max = max;
            },
        },
        y: {
            stacked: true,
            ticks: {
                autoSkip: false,
            },
        },
    },
    indexAxis: "y" as const
};

interface inputProps {
    roles?: boolean
}

function StaffGraphPage({ roles = false }: inputProps) {
    function validityCheck(staffdata: NrenStaff) {
        return (roles && (staffdata.technical_fte > 0 || staffdata.non_technical_fte > 0)) ||
            (!roles && (staffdata.permanent_fte > 0 || staffdata.subcontracted_fte > 0));
    }
    const { filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
    const { data, years, nrens } = useData<NrenStaff>('/api/staff', setFilterSelection, validityCheck);

    const selectedData = data.filter(data =>
        filterSelection.selectedYears.includes(data.year) && filterSelection.selectedNrens.includes(data.nren)
    );

    const nrenStaffDataset = createNRENStaffDataset(selectedData, roles, filterSelection.selectedYears[0]);

    const filterNode = <Filter
        max1year
        filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
        filterSelection={filterSelection}
        setFilterSelection={setFilterSelection}
    />

    const numNrens = selectedData.length;
    const heightPerBar = 1.5; // every added bar should give this much additional height

    // set a minimum height of 20rem
    const height = Math.max(numNrens * heightPerBar, 20);

    const title = roles
        ? titles.roles
        : titles.employment;
    const description = roles
        ? `The graph shows division of staff FTEs (Full Time Equivalents) between technical and non-techical role per NREN.
        The exact figures of how many FTEs are dedicated to these two different functional areas can be accessed by downloading the data in either CSV or Excel format`
        : `The graph shows the percentage of NREN staff who are permanent, and those who are subcontracted.
        The structures and models of NRENs differ across the community, which is reflected in the types of employment offered.
        The NRENs are asked to provide the Full Time Equivalents (FTEs) rather absolute numbers of staff.`;

    const filename = roles ? "roles_of_nren_employees" : "types_of_employment_for_nrens";
    return (
        <DataPage title={title}
            description={description}
            category={Sections.Organisation} filter={filterNode}
            data={selectedData} filename={filename}>

            <WithLegend>
                <div className="chart-container" style={{ 'height': `${height}rem` }}>
                    <Bar
                        data={nrenStaffDataset}
                        options={chartOptions}
                        plugins={[htmlLegendPlugin]}
                    />
                </div>
            </WithLegend>
        </DataPage>
    );
}
export default StaffGraphPage;