viewof traffickInOutBarChart = {
const traffickedInCounts = new Map();
const traffickedOutCounts = new Map();
summaryTable.forEach(({ citizenship, exploitationCountry, count }) => {
traffickedOutCounts.set(
citizenship,
(traffickedOutCounts.get(citizenship) || 0) + count
);
traffickedInCounts.set(
exploitationCountry,
(traffickedInCounts.get(exploitationCountry) || 0) + count
);
});
const countries = Array.from(
new Set([...traffickedInCounts.keys(), ...traffickedOutCounts.keys()])
);
countries.sort(
(a, b) =>
(traffickedInCounts.get(b) || 0) - (traffickedInCounts.get(a) || 0)
);
const data = countries.map((country) => ({
country,
in: traffickedInCounts.get(country) || 0,
out: traffickedOutCounts.get(country) || 0
}));
const margin = { top: 30, right: 20, bottom: 90, left: 50 };
const singleCountryWidth = 30;
const width = Math.max(360, singleCountryWidth * countries.length);
const height = 240 - margin.top - margin.bottom;
const x0 = d3
.scaleBand()
.domain(countries)
.range([0, width])
.paddingInner(0.3);
const x1 = d3
.scaleBand()
.domain(["in", "out"])
.range([0, x0.bandwidth()])
.padding(0.1);
const y = d3
.scaleLinear()
.domain([0, d3.max(data, (d) => Math.max(d.in, d.out)) * 1.1])
.nice()
.range([height, 0]);
const color = d3
.scaleOrdinal()
.domain(["in", "out"])
.range(["#003F7D", "#eda946"]);
const container = document.createElement("div");
container.style.overflowX = "auto";
container.style.border = "1px solid #ccc";
container.style.padding = "10px";
container.style.maxWidth = "100%";
const svg = d3
.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("font-family", "sans-serif");
container.appendChild(svg.node());
const g = svg
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// X Axis
g.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x0))
.selectAll("text")
.attr("transform", "rotate(-65)")
.style("text-anchor", "end")
.style("font-size", "11px");
// Y Axis
g.append("g")
.attr("class", "y-axis")
.call(d3.axisLeft(y).ticks(null, "s"))
.call((g) => g.select(".domain").remove());
// Y axis label, horizontally rotated left outside the axis ticks
svg
.append("text")
.attr("text-anchor", "middle")
.attr(
"transform",
`translate(${margin.left / 3},${margin.top + height / 2}) rotate(-90)`
)
.style("font-weight", "bold")
.style("font-size", "13px")
.text("Number of Victims");
// Tooltip div
let tooltip = d3.select("body").select(".bar-tooltip");
if (tooltip.empty()) {
tooltip = d3
.select("body")
.append("div")
.attr("class", "bar-tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("padding", "6px 10px")
.style("background", "rgba(255,255,255,0.95)")
.style("border", "1px solid #666")
.style("border-radius", "4px")
.style("font", "13px sans-serif")
.style("color", "#111")
.style("box-shadow", "1px 1px 4px rgba(0,0,0,0.15)")
.style("display", "none")
.style("z-index", "1000");
}
const countryGroups = g
.selectAll("g.country")
.data(data)
.join("g")
.attr("class", "country")
.attr("transform", (d) => `translate(${x0(d.country)},0)`);
countryGroups
.selectAll("rect")
.data((d) =>
["in", "out"].map((key) => ({ key, value: d[key], country: d.country }))
)
.join("rect")
.attr("x", (d) => x1(d.key))
.attr("y", (d) => y(d.value))
.attr("width", x1.bandwidth())
.attr("height", (d) => height - y(d.value))
.attr("fill", (d) => color(d.key))
.on("mouseover", (event, d) => {
tooltip
.style("display", "block")
.html(
`<strong>${d.country}</strong><br>${
d.key === "in" ? "Trafficked In" : "Trafficked Out"
}: <strong>${d.value}</strong>`
);
})
.on("mousemove", (event) => {
tooltip
.style("left", event.pageX + 10 + "px")
.style("top", event.pageY - 28 + "px");
})
.on("mouseout", () => {
tooltip.style("display", "none");
});
// Legend
const legend = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top / 2})`);
const legendItems = legend
.selectAll("g.legend-item")
.data(["Trafficked In", "Trafficked Out"])
.join("g")
.attr("class", "legend-item")
.attr("transform", (d, i) => `translate(${i * 140}, 0)`);
legendItems
.append("rect")
.attr("x", 0)
.attr("y", -12)
.attr("width", 18)
.attr("height", 18)
.attr("fill", (d, i) => color(i === 0 ? "in" : "out"));
legendItems
.append("text")
.attr("x", 24)
.attr("y", 0)
.attr("dy", "-0.25em")
.style("font-size", "13px")
.text((d) => d);
return container;
}