mp2i-vms.fr/santa_graph.html

170 lines
5.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Graphe du Secret Santa</title>
<title>D3.js Interactive Cycle Graph with Alternating Curved Arrows</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
svg {
display: block;
}
.node {
cursor: pointer;
}
.legend text {
font-size: 10px;
font-family: sans-serif;
text-anchor: start;
}
</style>
</head>
<body>
<script>
// Données du graphe
d3.csv("santa.csv").then(function(data) {
// Dimensions du SVG en plein écran
const width = window.innerWidth;
const height = window.innerHeight;
const uniqueNames = data.map(d => d.Source);
// Création des nœuds avec des positions initiales
const nodes = uniqueNames.map((name, index) => ({id: index, label: name}));
const links = data.map((d, i) => ({
source: i,
target: uniqueNames.indexOf(d.Cible)
}));
links.push({ source: 0, target: 1 });
// Création de la force de simulation avec une force de répulsion plus forte
const simulation = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-300))
.force("link", d3.forceLink(links).distance(100))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collide", d3.forceCollide().radius(20))
.force("x", d3.forceX().strength(0.1).x(width / 2))
.force("y", d3.forceY().strength(0.1).y(height / 2));
// Création du SVG avec zoom
const svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function () {
svg.attr("transform", d3.event.transform)
}))
.append("g");
// Ajout des flèches
svg.append("defs").selectAll("marker")
.data(["arrow"])
.enter().append("marker")
.attr("id", function (d) {
return d;
})
.attr("viewBox", "0 -5 10 10")
.attr("refX", 18)
.attr("refY", 0)
.attr("markerWidth", 4)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("class", "arrow-head");
// Ajout des liens
const link = svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("fill", "none")
.attr("stroke", "#ee288b")
.attr("stroke-width", "2")
.attr("marker-end", "url(#arrow)");
// Ajout des nœuds
const node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("circle")
.attr("r", 3) // Taille des points noirs
.attr("fill", "#000"); // Couleur des points noirs
// Ajout des légendes pour les nœuds
const legend = svg.selectAll(".legend")
.data(nodes)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d) {
return "translate(" + (d.x + 10) + "," + (d.y) + ")";
});
legend.append("text")
.text(function (d) {
return d.label;
});
// Met à jour la position des éléments à chaque itération de la simulation
simulation.on("tick", function () {
link.attr("d", function (d) {
const dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy),
sweep = 1;
return "M" + d.source.x + "," + d.source.y +
"A" + dr + "," + dr + " 0 0," + sweep + " " + d.target.x + "," + d.target.y;
});
node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
legend.attr("transform", function (d) {
return "translate(" + (d.x + 10) + "," + (d.y) + ")";
});
});
// Fonctions de gestion du glisser-déposer
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
});
</script>
</body>
</html>