-
Bjarke Madsen authoredBjarke Madsen authored
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;