Data-Driven Disappointment in Pictures: Hierarchy × Layoffs
RECAP: We saw previously that 25 out of 42 staff, or approximately 59.5% of the entire staff (or, at any rate, of the staff listed on the web page), were laid off from Canada Learning Code in January of 2024. This is neatly visualized in the pie (donut?) chart below 👇:
See the code:
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = await d3.tsv("https://jernwerber.dev/static/clc-laid-off-2024-01-20.tsv");
const svgWidth = 700;
const svgHeight = 500;
const outerRadius = 150;
const innerRadius = 100;
const shiftX = 0;
const shiftY = 40;
const radiusOffset = 50;
const baseColor = "crimson";
const backgroundColor = "none";
const pieGap = 0.04;
const pieRotate = 0.2 * Math.PI;
const CS = {
HIERARCHY : {
0 : "non-managers",
1 : "managers",
2 : "directors",
3 : "C-level",
4 : "CEO"
},
LAIDOFF: {
"TRUE" : "Laid off",
"FALSE" : "Not laid off"
}
}
const groupedData = d3.group(data, d => d.LaidOff);
const d3svg = d3.create("svg")
.attr("aria-labelledby","chart-title")
// .attr("width", svgWidth)
// .attr("height", svgHeight)
.attr("viewBox", [-svgWidth / 2, -svgHeight / 2, svgWidth, svgHeight])
.style("background-color", backgroundColor)
.style("margin-inline","auto")
;
d3svg.append("text")
.attr("id", "chart-title")
.attr("text-anchor", "middle")
.attr("transform",`translate(0 ${ -svgHeight/2.2 + shiftY })`)
.style("font-size","24")
.style("font-weight", "light")
.style("font-style","italic")
.selectAll("tspan")
.data(["Canada Learning Code 2024 Layoffs:", "Employees LAID OFF"])
.join("tspan")
.text(d => d)
.attr("x", 0)
.attr("dy", (d,i) => 28 * i)
;
d3svg.append("g")
.attr("transform",`translate(${shiftX},${shiftY})`)
.selectAll("path")
.data(
d3.pie()
.value(d => d[1].length)
.startAngle(pieRotate)
.endAngle(2*Math.PI + pieRotate)
.sort(null)(groupedData)
)
.join("path")
.attr("fill", (d, i) => d3.quantize(
d3.interpolateRgb("lightgrey","crimson"),2
).reverse()[i]
)
.attr("stroke", "none")
.attr("d", d3.arc().innerRadius(innerRadius).outerRadius(outerRadius))
.append("title")
.text(d => d.value)
;
d3svg.append("g")
.attr("transform",`translate(${shiftX},${shiftY})`)
.selectAll("g")
.data(
d3.pie()
.value(d => d[1].length)
.startAngle(pieRotate)
.endAngle(2 * Math.PI + pieRotate)
.sort(null)(groupedData)
)
.join("g")
.each(function (d, i) {
const [x,y] = d3.arc().innerRadius(outerRadius).outerRadius(outerRadius + radiusOffset).centroid(d);
const g = d3.select(this);
const labelWidth = 36;
const labelHeight = 24;
g.append("rect")
.attr("x", x - labelWidth/2)
.attr("y", y - labelHeight/2)
.attr("width",labelWidth)
.attr("height",labelHeight)
.attr("fill", "none")
;
g.append("text")
.attr("dy",6)
.attr("text-anchor",(d.endAngle + d.startAngle)/2 < Math.PI ? "start" : "end" )
.text(`${CS.LAIDOFF[d.data[0]]} `)
.attr("x", x)
.attr("y", y)
.attr("fill","darkslategrey")
.style("font-weight","light")
.style("font-style", "italic")
.style("font-family","Georgia")
.append("tspan")
.style("font-weight", "bold")
.style("cursor", "help")
.text(`${d.value}`)
.append("title")
.text(`${(d.value * 100 / d3.sum(groupedData, d => d[1].length)).toFixed(2)}%`)
;
})
;
d3.select("#d3-container-997").node()
.append(d3svg.node())
;
</script>
<div id="d3-container-997">
<!-- this will hold whatever D3 generates -->
</div>
The overall proportion statistic, while staggering in its magnitude, doesn’t give us all that much more detail as to the impact of the layoffs on the company’s overall structure: We do know that the company didn’t shutdown, so there must be some intention to stick around at least for a little longer, but stick around for what purpose–who’s left and what do they do?
To get a better handle on what happened, we need to ask more questions.
Perhaps the next questions we might ask should be: Who was laid off? Or, how were layoffs distributed across the company?
Hierarchy by Laid Off Status
To help us answer these questions, let’s take a look at the first chart, below 👇:
See the code:
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = await d3.tsv("https://jernwerber.dev/static/clc-laid-off-2024-01-20.tsv");
const groupedData = d3.group(data, d => d.Hierarchy);
const svgWidth = 700;
const svgHeight = 500;
const outerRadius = 100;
const innerRadius = 50;
const shiftX = 0;
const shiftY = 40;
const baseColor = "crimson";
const backgroundColor = "none";
const pieGap = 0.04;
const pieRotate = 0.2 * Math.PI;
const CS = {
HIERARCHY : {
0 : "non-managers",
1 : "managers",
2 : "directors",
3 : "C-level",
4 : "CEO"
}
}
const d3svg = d3.create("svg")
.attr("aria-labelledby","chart-title")
// .attr("width", svgWidth)
// .attr("height", svgHeight)
.attr("viewBox", [-svgWidth / 2, -svgHeight / 2, svgWidth, svgHeight])
.style("background-color", backgroundColor)
.style("margin-inline","auto")
;
d3svg.append("text")
.attr("id", "chart-title")
.attr("text-anchor", "middle")
.attr("transform",`translate(0 ${ -svgHeight/2.2 + shiftY })`)
.style("font-size","24")
.style("font-weight", "light")
.style("font-style","italic")
.selectAll("tspan")
.data(["Canada Learning Code 2024 Layoffs:", "Employees HIERARCHY × LAID OFF"])
.join("tspan")
.text(d => d)
.attr("x", 0)
.attr("dy", (d,i) => 28 * i)
;
d3svg.append("g")
.attr("transform",`translate(${shiftX},${shiftY})`)
.selectAll("path")
.data(
d3.pie()
.value(d => d[1].length)
.startAngle(pieRotate)
.endAngle(2*Math.PI + pieRotate)
.sort(null)(groupedData)
)
.join("path")
.each(function(i,j) {
d3svg.append("g")
.attr("transform",`translate(${shiftX},${shiftY})`)
.selectAll("path")
.data(
d3.pie()
.value(i => i[1].length)
.startAngle(i.startAngle + pieGap/2)
.endAngle(i.endAngle - pieGap/2)
.sort((a,b) => d3.ascending(a[0],b[0]))(d3.group(i.data[1], d => d.LaidOff))
)
.join("path")
.attr("fill", (k,m) => k.data[0] === "TRUE" ? "lightgrey" : d3.color(
d3.quantize(
d3.interpolateReds,11).reverse()[j+1]
)
)
.attr("stroke", (k,m) => k.data[0] === "TRUE" ? "none" : "none")
.attr("d", d3.arc().innerRadius(outerRadius + 10).outerRadius(outerRadius + 50))
.append("title")
.text(d => d.data[1].length)
})
.attr("fill", (d, i) => d3.quantize(
d3.interpolateReds,11
).reverse()[i+1]
)
.attr("stroke", "none")
.attr("d", d3.arc().innerRadius(50).outerRadius(100))
.append("title")
.text(d => d.value)
;
d3svg.append("g")
.attr("transform",`translate(${shiftX},${shiftY})`)
.selectAll("g")
.data(
d3.pie()
.value(d => d[1].length)
.startAngle(pieRotate)
.endAngle(2 * Math.PI + pieRotate)
.sort(null)(groupedData)
)
.join("g")
.each(function (d, i) {
const [x,y] = d3.arc().innerRadius(150).outerRadius(180).centroid(d);
const g = d3.select(this);
const labelWidth = 36;
const labelHeight = 24;
g.append("rect")
.attr("x", x - labelWidth/2)
.attr("y", y - labelHeight/2)
.attr("width",labelWidth)
.attr("height",labelHeight)
.attr("fill", "none")
;
g.append("text")
.attr("dy",6)
.attr("text-anchor",(d.endAngle + d.startAngle)/2 < Math.PI ? "start" : "end" )
.text(`${CS.HIERARCHY[d.data[0]]} `)
.attr("x", x)
.attr("y", y)
.attr("fill","darkslategrey")
.style("font-weight","light")
.style("font-style", "italic")
.style("font-family","Georgia")
.append("tspan")
.style("text-decoration-line", "underline")
.style("cursor", "help")
.text(`(${d.value - (d3.group(data, e => e.Hierarchy, e => e.LaidOff).get(d.data[0]+"").get("FALSE").length)} of ${d.value})`)
.append("title")
.text(`${(d.value - (d3.group(data, e => e.Hierarchy, e => e.LaidOff).get(d.data[0]+"").get("FALSE").length)) * 100 / d.value}%`)
;
})
;
d3.select("#d3-container-999").node()
.append(d3svg.node())
;
</script>
<div id="d3-container-999">
<!-- this will hold whatever D3 generates -->
</div>
Chart description
- The inner ring in the nested chart below is separated by a role’s
Hierarchy
, a number from0
to4
indicating an inferred (based on role title) relative power level of an employee within the organization (0
corresponds to non-managers, while1
to4
corresponds to increasing degrees of decision-making power and responsbility) - The outer ring is each
Hierarchy
segment divided further into those laid off (coloured inlightgrey
) and those not laid off (coloured in some shade of red).
Summary of data
- 84% of non-manager employees (the group that I was part of) were laid off. It was a bad time™ to not be a manager.
- 30% of managers were laid off, so not a super great time for them either, but they were less than half as likely to be laid off as non-managers. Note: because this hierarchy level was assigned based on the use of the word “manager” in a role’s title, there may be some non-people-managers mixed in here who might make more sense at the non-manager level.
- 20% of directors (1 out of 5) were laid off, so they fared slightly better than managers.
- 0% of executives, of which there were only 2 (CEO + 1) at the time of data collection, were laid off.
Key takeaways
- Non-managers–the lowest level of employee–were significantly disproportionately affected by the Canada Learning Code lay offs compared to other hierarchy groups. Non-managers include
roles such as
Learning facilitator
,Data analyst
, andLearning experience designer
as well as theirSenior
andBilingual
variants. - Given the relative safety of manager-level employees, there are almost certainly people managers who were not laid off who now have greatly reduced (or non-existent) teams.
- The safest place to be during these layoffs was in senior leadership or higher (directors and up), with only 1 out of 7 (or approx. 14.3%) of that group being laid off.
In the next post, I’ll split the data by LAID OFF status first and then by HIERARHCY to present this data another way: Data-Driven Disappointment in Pictures: Layoffs × Hierarchy