Public
Edited
Jan 23, 2024
Insert cell
Insert cell
Insert cell
vis = html``
Insert cell
thumbnail_daemon = navigator.userAgent.match("HeadlessChrome")
Insert cell
{
const legendHitBoxes = [];
const visualiser = new ChartJS.Visualiser(options(legendHitBoxes));
await visualiser.build(500, width);
vis.replaceChildren(visualiser.container);
setTimeout(() => {
for (const legendHitBox of visualiser.visual.legend.legendHitBoxes)
legendHitBoxes.push(legendHitBox);
visualiser.visual.update();
}, 0);
return visualiser;
}
Insert cell
Insert cell
data = Data.buildMeasureMap(WorkforceData.sizeByMonthForCalendarYear.months, [
{ id: "openingHeadcount" },
{
id: "startingHeadcount",
source: row => row.openingHeadcount + row.startingHires
},
{
id: "endingHeadcount",
source: row => row.closingHeadcount + row.endingTerminations
},
{ id: "closingHeadcount" },
{
id: "openingClosingHeadcount",
source: row => [row.openingHeadcount, row.closingHeadcount]
},
{
id: "startingEndingHeadcount",
source: row => [
row.openingHeadcount + row.startingHires,
row.closingHeadcount + row.endingTerminations
]
}
])
Insert cell
Insert cell
options = (legendHitBoxes) => ({
type: "bar",
data: {
labels: Data.monthAbbreviations,
datasets: [
{
backgroundColor: Style.colours.opening,
borderColor: Style.colours.opening,
borderDash: [4, 4],
data: data.openingHeadcount,
hidden: true,
label: "Opening",
pointRadius: 6,
pointStyle: "circle",
type: "line"
},
{
backgroundColor: (context) => {
const value = context.dataset.data[context.dataIndex];
if (value[1] < value[0]) return Style.colours.startStopDecrease;
return Style.colours.startStopIncrease;
},
barPercentage: 0.5,
data: data.startingEndingHeadcount,
hidden: true,
label: "Start/End"
},
{
backgroundColor: (context) => {
const value = context.dataset.data[context.dataIndex];
if (value[1] < value[0]) return Style.colours.openCloseDecrease;
return Style.colours.openCloseIncrease;
},
borderColor: "#aaa",
borderSkipped: false,
borderWidth: { bottom: 1, left: 0, right: 0, top: 1 },
data: data.openingClosingHeadcount,
label: "Open/Close2"
}
]
},
options: {
datasets: { bar: { grouped: false } },
displayConnectionLines: true,
layout: { padding: 16 },
plugins: {
legend: {
labels: {
generateLabels: (chart) => generateLegendItems(chart, legendHitBoxes)
},
onClick: handleLegendItemClick
},
title: {
display: true,
text: `Headcount Range by Month for ${WorkforceData.sizeByMonthForCalendarYear.year}`
},
tooltip: {
enabled: false,
external: (context) => {
ChartJS.headcountTooltipHandler(
context,
WorkforceData.sizeByMonthForCalendarYear.months
);
},
intersect: false
}
},
scales: {
x: { grid: { drawOnChartArea: false } },
y: { beginAtZero: false, grid: { drawOnChartArea: false } }
}
},
plugins: [{ afterDraw: ChartJS.drawConnectionLines }]
})
Insert cell
generateLegendItems = (chart, legendHitBoxes) => {
return [
{
datasetIndex: 2,
fillStyle: ChartJS.getLegendSymbol(legendHitBoxes, 0),
hidden: !chart.isDatasetVisible(2),
lineWidth: 0,
text: "Open/Close"
},
{
datasetIndex: 1,
fillStyle: ChartJS.getLegendSymbol(legendHitBoxes, 1),
hidden: !chart.isDatasetVisible(1),
lineWidth: 0,
text: "Start/Stop"
},
{
datasetIndex: 0,
fillStyle: Style.colours.opening,
hidden: !chart.isDatasetVisible(0),
lineWidth: 0,
text: "Opening"
}
];
}
Insert cell
handleLegendItemClick = (event, legendItem, legend) => {
const datasetIndex = legendItem.datasetIndex;
if (datasetIndex === 2) {
event.native.stopPropagation();
return;
}

const chart = legend.chart;
if (chart.isDatasetVisible(datasetIndex)) {
chart.hide(datasetIndex);
legendItem.hidden = true;
} else {
chart.show(datasetIndex);
legendItem.hidden = false;
}
chart.update();
}
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more