// A HORIZONTAL beeswarm: value on the X axis, one lane per department on Y.
// Every dot is a person's base pay. Each department gets its own hue (ordered so
// adjacent lanes separate well for colour-vision deficiency) and the eye reads
// each lane's spread, centre and outliers from the swarm shape.
// Figures are an illustrative sample (USD, base salary).
// Shared by the vanilla-js / React / Vue builds.
var DEPTS = [
{ name: 'Engineering', mean: 152, sd: 30, n: 34 },
{ name: 'Data Science', mean: 158, sd: 26, n: 22 },
{ name: 'Product', mean: 141, sd: 22, n: 18 },
{ name: 'Design', mean: 118, sd: 20, n: 20 },
{ name: 'Sales', mean: 112, sd: 34, n: 30 },
{ name: 'Support', mean: 74, sd: 12, n: 26 },
]
// Deterministic normal-ish sample (Irwin-Hall via an LCG): stable across
// re-renders and SSR, no Math.random. Rounded to the nearest $1k, floored so no
// dot dips below a plausible wage.
function swarm(mean, sd, n, seed) {
var out = []
var s = seed >>> 0
function u() {
s = (s * 1664525 + 1013904223) >>> 0
return s / 4294967296
}
for (var i = 0; i < n; i++) {
var g = 0
for (var k = 0; k < 6; k++) g += u()
var z = (g - 3) / Math.sqrt(0.5)
out.push(Math.max(48, Math.round(mean + z * sd)))
}
return out
}
var paySeries = DEPTS.map(function (d, i) {
return {
name: d.name,
data: swarm(d.mean, d.sd, d.n, 41 + i * 977).map(function (v, j) {
return { name: d.name + ' #' + (j + 1), value: v }
}),
}
})
var options = {
series: paySeries,
chart: {
id: 'salarySwarm',
type: 'unit',
height: 470,
fontFamily: 'inherit',
animations: {
enabled: true,
speed: 700,
},
},
// One hue per team, ordered so adjacent lanes stay far apart for colour-vision
// deficiency (validated: worst adjacent pair ΔE 9.2 CVD / 27.6 normal). The lane
// label repeats the colour, so identity never rests on hue alone.
colors: ['#008300', '#2a78d6', '#eb6834', '#1baf7a', '#4a3aa7', '#e87ba4'],
legend: {
position: 'bottom',
markers: { radius: 12 },
},
plotOptions: {
unit: {
layout: 'scatter',
size: 4,
scatter: {
orientation: 'horizontal',
spread: 'swarm',
xMin: 40,
xMax: 240,
tickAmount: 5,
laneLabelWidth: 104,
xTitle: 'Base salary',
xFormatter: function (v) {
return '$' + v + 'k'
},
},
},
},
tooltip: {
enabled: true,
},
}
var chart = new ApexCharts(document.querySelector('#chart'), options)
chart.render()