Public
Edited
Jan 23, 2024
1 fork
Insert cell
Insert cell
Insert cell
thumbnail_daemon = navigator.userAgent.match("HeadlessChrome")
Insert cell
Insert cell
visualise = async container => {
const visualiser = await new Highcharts.Visualiser(
container,
options
).build();
container.replaceChildren(visualiser.element);
return visualiser;
}
Insert cell
Insert cell
data = {
const data = {
broughtForwardHeadcounts1: buildEmptyArray(14),
broughtForwardHeadcounts2: buildEmptyArray(14),
openingHeadcounts: buildEmptyArray(14),
hires: buildEmptyArray(14),
//startingHires: buildEmptyArray(14),
averageHeadcounts: buildEmptyArray(14),
offsets: buildEmptyArray(14),
//endingTerminations: buildEmptyArray(14),
terminations: buildEmptyArray(14),
closingHeadcounts: buildEmptyArray(14),
carryForwardHeadcounts1: buildEmptyArray(14),
carryForwardHeadcounts2: buildEmptyArray(14)
};
for (const [
month,
record
] of WorkforceData.sizeByMonthForCalendarYear.months.entries()) {
if (month === 0) {
data.broughtForwardHeadcounts1[0] = record.openingHeadcount;
data.broughtForwardHeadcounts2[0] =
record.openingHeadcount + record.hires;
}
if (month > 0) {
data.offsets[month] = record.hires;
}
if (month === 11) {
data.carryForwardHeadcounts1[month + 2] = record.closingHeadcount * -1;
data.carryForwardHeadcounts2[month + 2] = record.closingHeadcount * -1;
}
data.hires[month + 1] = record.hires;
data.averageHeadcounts[month + 1] = record.averageHeadcount;
data.openingHeadcounts[month + 1] = record.openingHeadcount;
data.closingHeadcounts[month + 1] = record.closingHeadcount;
data.terminations[month + 1] = record.terminations * -1;
}
return { ...data };
}
Insert cell
buildEmptyArray = size => {
return new Array(size).fill(null);
}
Insert cell
round = (value, decimalPlaces) => {
if (!value) return value;
return value.toLocaleString(undefined, {
maximumFractionDigits: decimalPlaces || 0,
minimumFractionDigits: decimalPlaces || 0
});
}
Insert cell
Insert cell
options = {
return {
chart: {
animation: false,
backgroundColor: "transparent",
reflow: false,
style: { fontSize: '1em' },
type: 'waterfall'
},
legend: {
itemStyle: { fontSize: '0.875em', fontWeight: 'normal' },
symbolRadius: 0
},
plotOptions: {
series: { animation: false, groupPadding: 0.2, pointPadding: 0.05 },
waterfall: { dashStyle: 'dash', stacking: 'normal' }
},
series: [
{
borderColor: '#BDBDBD',
color: '#EEEEEE',
data: data.broughtForwardHeadcounts1,
legendIndex: 0,
lineColor: 'transparent',
lineWidth: 0,
name: 'Headcount',
//pointPadding: -0.5,
//pointPlacement: 0.1,
stack: 0
},
{
borderColor: '#66BB6A',
color: '#C8E6C9',
data: data.hires,
legendIndex: 1,
lineColor: 'transparent',
lineWidth: 0,
name: 'Hires',
stack: 0
},
{
borderColor: 'transparent',
color: 'transparent',
data: data.terminations,
lineColor: '#BDBDBD',
lineWidth: 1,
showInLegend: false,
stack: 0
},
{
borderColor: '#BDBDBD',
color: '#EEEEEE',
data: data.carryForwardHeadcounts1,
lineColor: 'transparent',
lineWidth: 0,
name: "Headcount",
//pointPadding: -0.5,
//pointPlacement: 0.15,
showInLegend: false,
stack: 0
},
{
borderColor: 'transparent',
color: 'transparent',
data: data.broughtForwardHeadcounts2,
lineColor: 'transparent',
lineWidth: 0,
showInLegend: false,
stack: 1
},
{
borderColor: '#FFA726',
color: '#FFCC7F',
data: data.terminations,
legendIndex: 2,
lineColor: 'transparent',
lineWidth: 0,
name: 'Terminations',
stack: 1
},
{
borderColor: 'transparent',
color: 'transparent',
data: data.offsets,
lineColor: 'transparent',
lineWidth: 0,
showInLegend: false,
stack: 1
},
{
borderColor: 'transparent',
color: 'transparent',
data: data.carryForwardHeadcounts2,
lineColor: 'transparent',
lineWidth: 0,
name: "Headcount",
//pointPadding: -0.5,
//pointPlacement: 0.15,
showInLegend: false,
stack: 1
},
{
color: 'transparent',
data: data.openingHeadcounts,
name: 'Opening',
showInLegend: false,
type: 'line'
},
{
color: 'transparent',
data: data.closingHeadcounts,
name: 'Closing',
showInLegend: false,
type: 'line'
},
{
color: '#2196F3',
data: data.averageHeadcounts,
legendIndex: 3,
name: 'Average Headcount',
type: 'line'
}
],
title: {
style: { fontSize: '1.125em' },
text: `Headcount Progression by Month for ${WorkforceData.sizeByMonthForCalendarYear.year}`
},
tooltip: {
formatter: function(tooltip) {
return buildTooltip(this);
},
shared: true,
useHTML: true
},
xAxis: [
{
categories: ["Open"].concat(Data.monthAbbreviations.concat(["Close"])),
crosshair: true,
labels: { style: { fontSize: '0.875em' } }
}
],
yAxis: {
labels: { format: '{value:,.0f}', style: { fontSize: '0.875em' } },
min: 1075,
title: { text: 'Headcount' }
}
};
}
Insert cell
function buildTooltip(context) {
try {
const items = {};
for (const point of context.points) {
items[point.series.name] = point.y;
}
let tooltip = `
<div class="tooltip">
<div style="padding-bottom: 5px;">${context.x}</div>
<table>`;
if (items.Opening) {
const opening = items.Opening.toLocaleString();
tooltip += `<tr><td>Opening</td><td>${opening}</td></tr>`;
}
if (items.Hires) {
const hires = items.Hires.toLocaleString();
tooltip += `<tr><td>+ Hires</td><td>${hires}</td></tr>`;
}
if (items.Terminations) {
const terminations = (items.Terminations * -1).toLocaleString();
tooltip += `<tr><td>- Terminations</td><td>${terminations}</td></tr>`;
}
if (items.Closing) {
tooltip += `
<tr>
<td>= Closing</td>
<td>${items.Closing.toLocaleString()}</td>
</tr>`;
}
if (items["Average Headcount"]) {
const averageHeadcount = round(items["Average Headcount"], 2);
tooltip += `<tr>
<td style="padding-top: 5px;">Average</td><td style="padding-top: 5px;">${averageHeadcount}</td>
</tr>`;
}
tooltip += `</table></div>`;
return tooltip;
} catch (error) {
return String(error);
}
}
Insert cell
Insert cell
// new ResizeObserver(outputSize => visualiser.resize()).observe(presentation)
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