{
const interRadius = Math.min(width, height) * 0.5 - 100;
const dentroRadius = interRadius - 30;
const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("width", width)
.attr("height", height)
.style("font", "12px sans-serif");
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("pointer-events", "none")
.style("background", "#fff")
.style("padding", "6px 10px")
.style("border", "1px solid #ccc")
.style("border-radius", "6px")
.style("box-shadow", "2px 2px 5px rgba(0,0,0,0.1)")
.style("font", "12px sans-serif")
.style("visibility", "hidden");
const matrix = Array.from({ length: nodes.length }, () => new Array(nodes.length).fill(0));
for (const d of aiddata) {
if (top20Donadores.has(d.donor) && top10Recipients.has(d.recipient)) {
const i = index.get(d.donor);
const j = index.get(d.recipient);
matrix[i][j] += d.amount;
}
}
const chord = d3.chordDirected()
.padAngle(0.05)
.sortSubgroups(d3.descending)(matrix);
const arc = d3.arc().innerRadius(dentroRadius).outerRadius(interRadius);
const ribbon = d3.ribbon().radius(dentroRadius);
const color = d3.scaleOrdinal()
.domain(nodes)
.range(nodes.map(n => donors.includes(n) ? "#1f77b4" : "#d62728"));
const groupPaths = svg.append("g")
.selectAll("path")
.data(chord.groups)
.join("path")
.attr("fill", d => color(nodes[d.index]))
.attr("stroke", "#000")
.attr("d", arc)
.style("cursor", "pointer")
.on("mouseover", (event, d) => {
const i = d.index;
highlight(i);
const name = nodes[i];
const total = d3.sum(matrix[i]) + d3.sum(matrix.map(row => row[i]));
tooltip
.html(`<b>${name}</b><br>Total: $${d3.format(",.0f")(total)}`)
.style("visibility", "visible");
})
.on("mousemove", event =>
tooltip
.style("top", (event.pageY + 10) + "px")
.style("left", (event.pageX + 10) + "px"))
.on("mouseout", () => {
unhighlight();
tooltip.style("visibility", "hidden");
});
const ribbonPaths = svg.append("g")
.selectAll("path")
.data(chord)
.join("path")
.attr("fill", d => color(nodes[d.source.index]))
.attr("d", ribbon)
.style("fill-opacity", 0.65)
.style("cursor", "default")
.on("mouseover", (event, d) => {
highlight(d.source.index);
tooltip
.html(`<b>${nodes[d.source.index]}</b> → <b>${nodes[d.target.index]}</b><br>$${d3.format(",.0f")(d.source.value)}`)
.style("visibility", "visible");
})
.on("mousemove", event =>
tooltip
.style("top", (event.pageY + 10) + "px")
.style("left", (event.pageX + 10) + "px"))
.on("mouseout", () => {
unhighlight();
tooltip.style("visibility", "hidden");
});
svg.append("g")
.selectAll("text")
.data(chord.groups)
.join("text")
.each(d => { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", "0.35em")
.attr("transform", d => `
rotate(${(d.angle * 180 / Math.PI - 90)})
translate(${interRadius + 12})
${d.angle > Math.PI ? "rotate(180)" : ""}
`)
.attr("text-anchor", d => d.angle > Math.PI ? "end" : "start")
.text(d => nodes[d.index]);
function highlight(index) {
groupPaths
.style("opacity", d => d.index === index ? 1 : 0.1);
ribbonPaths
.style("opacity", d =>
d.source.index === index || d.target.index === index ? 0.7 : 0.05);
}
function unhighlight() {
groupPaths.style("opacity", 1);
ribbonPaths.style("opacity", 0.65);
}
return svg.node();
}