Olá (meu inglês não é tão bom, então o resto é traduzido com o GoogleTrad, sou francês =D)
Para um projeto, estou criando um radialtree com html, código js e a biblioteca D3js.
Tento reproduzir a primeira árvore, a mais completa. O 2º é o que estou conseguindo até agora. Aqui está a primeira árvore que estou tentando reproduzir.
Aqui a 2ª árvore, que é um trabalho meu.
Se você olhar para o centro do círculo, na primeira árvore radial os galhos vão em uma "linha reta" a partir do centro. Na segunda árvore radial, as ramificações formam uma curva no início e depois vão para os nós filhos. Não consigo impedir esta curva inicial, você pode me ajudar?
Aqui está o meu código completo: https://jsfiddle.net/LoChab/jetmryhb/2/
<!DOCTYPE html>
<html>
<head>
<title>D3.js Radial Tree Example</title>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<style>
svg {
border: solid 1px rgb(255, 51, 0);
display: block;
margin: 0 auto;
}
.link {
fill: none;
stroke: #194353;
stroke-width: 5.5px;
}
.node {
fill: white;
stroke: black;
}
.node-text {
font-family: 'Roboto', sans-serif;
font-size: 12px;
fill: black;
stroke: none;
white-space: pre-line;
}
.background-circle {
fill: #f0f0f000;
stroke: rgba(255, 255, 255, 0);
stroke-width: 0px;
}
.center-circle {
fill: #194353;
}
</style>
</head>
<body>
<svg id="combinedSvg" width="1500" height="1500">
<g id="radialTreeGroup">
<!-- Contenu de l'arbre radial -->
</g>
<g id="pieChartGroup">
<!-- Contenu du graphique à secteurs -->
</g>
</svg>
<div id="exportButtonContainer">
<button id="exportButton">Exporter le graphe en PNG</button>
</div>
<script>
// radial tree
let root = {
"name": "Point 1", "info":"FirstNode", "weight": 117, "children": [
{"name":"CULTIVER", "weight": 21,
"children":[{"name":"LA QUALITE DES RELATIONS (n=13)", "weight": 13},
{"name":"ENTRAIDE ET COLLABORATION (n=3)", "weight": 3},
{"name":"L'ARTICULATION DE TEMPS COLLECTIFS ET INDIVIDUEL (n=3)", "weight": 3},
{"name":"LA CONVIVIALITE (n=2)", "weight": 2}]},
{"name":"LE CADRE ET L'ANIMATION", "weight": 12,
"children":[{"name":"UN CADRE STRUCTURE ET BIENVEILLANT QUI PERMET L'ECOUTE (n=3)", "weight": 3},
{"name":"LE RESPECT DE L'AUTONOMIE (n=2)", "weight": 2},
{"name":"LE RESPECT DU RYTHME DE CHACUN (n=2)", "weight": 2},
{"name":"LE PROFESSIONNALISME DE L'EQUIPE (n=5)", "weight": 5}]},
{"name":"LE COLLECTIF", "weight": 14,
"children":[{"name":"LE TRAVAIL (n=6)", "weight": 6},
{"name":"PRECISER (n=5)", "weight": 5},
{"name":"LA REPRISE (n=2)", "weight": 2},
{"name":"LA REGLE (n=1)", "weight": 1}]},
{"name":"RESTITUTION", "weight": 2,
"children":[{"name":"CREATION (n=1)", "weight": 1},
]
}]};
let maxDistance; // Déclaration de la variable maxDistance en dehors de la fonction
let createRadialTree = function (input) {
let height = 1500;
let width = 1500;
let svg = d3.select('#radialTreeGroup')
.append('svg')
.attr('width', width)
.attr('height', height);
let diameter = height * 8.1;
let radius = diameter / 30.1;
let tree = d3.tree()
.size([2 * Math.PI, radius])
.separation(function (a, b) {
if (a.parent === b.parent) {
return 1;
} else if (a.depth === b.depth) {
return 1.2;
} else {
return 2;
}
});
let data = d3.hierarchy(input);
let treeData = tree(data);
let nodes = treeData.descendants();
let links = treeData.links();
nodes.forEach(function (node) {
let totalChildren = node.descendants().length - 1;
node.totalChildren = totalChildren;
});
let linkWidthScale = d3.scaleLinear()
.domain([0, d3.max(nodes, function (d) { return d.totalChildren; })])
.range([0, 15]);
let graphGroup = svg.append('g')
.attr('transform', "translate(" + (width / 2) + "," + (height / 2) + ")");
maxDistance = calculateMaxDistance(nodes);
graphGroup.append("circle")
.attr("class", "background-circle")
.attr("r", maxDistance)
.style("fill", "#f0f0f000");
// Ajouter un cercle fixe au centre du graphe
graphGroup.append("circle")
.attr("class", "center-circle")
.attr("r", 10) // rayon du cercle au centre
.style("fill", "#194353");
graphGroup.selectAll(".link")
.data(links)
.join("path")
.attr("class", "link")
.style("stroke-width", function (d) {
return linkWidthScale(d.target.data.weight || 1);
})
.attr("stroke-linecap", "round")
.attr("d", d3.linkRadial()
.angle(function (d) {
if (d.depth === 0) { // Vérifie si c'est le nœud parent (racine)
return 0; // Angle fixe pour le nœud parent au centre
} else {
return d.x;
}
})
.radius(function (d) { return d.y; })
);
let nodeSizeScale = d3.scaleLinear()
.domain([0, d3.max(nodes, function(d) { return d.depth; })])
.range([15, 4]);
let node = graphGroup
.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", function(d) { return "node node-level-" + d.depth; })
.attr("transform", function (d) {
let angle = (d.x - Math.PI / 2) * 180 / Math.PI; // Ajustement de l'angle de rotation
let radius = d.y; // Rayon du cercle
return `rotate(${angle}) translate(${radius}, 0)`;
});
node.append("circle")
.attr("r", function(d) { return nodeSizeScale(d.depth); });
node.filter(function (d) { return d.depth === 2; })
.append("text")
.attr("class", "node-text")
.attr("dx", function (d) { return d.x < Math.PI ? 14 : -14; })
.attr("dy", ".31em")
.attr("text-anchor", function (d) { return d.x < Math.PI ? "start" : "end"; })
.attr("transform", function (d) { return d.x < Math.PI ? null : "rotate(180)"; })
.selectAll("tspan")
.data(function (d) {
return d.data.name.split("\n");
})
.enter()
.append("tspan")
.attr("x", 0)
.attr("dy", function (d, i) { return i ? "1.2em" : 0; })
.text(function (d) { return d; })
.call(wrapText, 300); // Appel à la fonction wrapText avec une largeur de 20 pixels
};
function calculateMaxDistance(nodes) {
let maxDistance = 0;
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].depth === 2) {
maxDistance = Math.max(maxDistance, nodes[i].y);
}
}
return maxDistance;
}
function wrapText(text, width) {
text.each(function (d) {
if (d.depth < 1) {
return;
}
let text = d3.select(this);
let words = text.text().split(/\s+/).reverse();
let lineHeight = 1.2; // Ajustez la valeur ici pour définir l'interligne souhaité
let y = text.attr("y");
let x = text.attr("x");
let dy = parseFloat(text.attr("dy")) || 0;
let tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
let line = [];
let lineNumber = 0;
let word;
let wordCount = words.length;
while ((word = words.pop())) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dx", x) // Ajout de l'attribut dx conditionnellement
.attr("dy", lineHeight + "em") // Utilisez une valeur fixe pour l'interligne
.text(word);
}
}
});
}
// code svg
const svgWidth = 1500;
const svgHeight = 1500;
const pieChartGroup = d3.select("#pieChartGroup")
.attr("transform", `"translate(" + (width / 2) + "," + (height / 2) + "`);
const radialTreeGroup = d3.select("#radialTreeGroup")
.attr("transform", `translate()`);
// code création radial tree
createRadialTree(root);
// Opération à appliquer à toutes les datas ayant le label "Bout"
function applyOperation(data) {
const updatedData = data.map(d => {
if (d.label === "Bout") {
// Effectuer l'opération souhaitée sur la valeur ici
d.value = ((2.3 / 2) + (((d.value)-1)*1.1) + (1.4/2));
}
else if (d.label === "") {
// Effectuer l'opération souhaitée sur la valeur ici
d.value = ((((d.value) - 1) * 1.1) + (1.4));
}
return d;
});
return updatedData;
}
// Données pour les secteurs du graphique
const data = [
{ label: "Bout", value: 4},
{ label: "", value: 4},
{ label: "", value: 4},
{ label: "", value: 3},
{ label: "", value: 2},
{ label: "", value: 3},
{ label: "", value: 3},
{ label: "", value: 2},
{ label: "", value: 3},
{ label: "", value: 2},
{ label: "", value: 1},
{ label: "", value: 3},
{ label: "", value: 2},
{ label: "Bout", value: 1},
// Ajoutez autant de secteurs que vous le souhaitez avec leurs angles respectifs
// Value = degré, valeur
];
// Appliquer l'opération aux données ayant le label "Bout"
const updatedData = applyOperation(data);
console.log(updatedData);
// Dimensions du graphique
const width = 1500;
const height = 1500;
const radius = maxDistance;
// Deux jeux de couleurs alternées
const colors = ["#98d9ff", "#d4efff"];
// Création d'un générateur d'angles
const pie = d3.pie()
.value(d => d.value)
.sort(null);
// Sélection de la zone du graphique
const svg = d3.select("#pieChartGroup")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
// Création des arcs pour les secteurs
const arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
// Génération du graphique
const arcs = svg.selectAll("arc")
.data(pie(data))
.enter()
.append("g");
arcs.append("path")
.attr("d", arc)
.attr("fill", (d, i) => colors[i % colors.length]); // Alterne entre les deux jeux de couleurs
// Ajout d'étiquettes à chaque secteur (optionnel)
// arcs.append("text")
// .attr("transform", d => `translate(${arc.centroid(d)})`)
// .attr("text-anchor", "middle")
// .text(d => d.data.label);
pieChartGroup.lower(0); // pour mettre les secteurs en arrière-plan
// EXPORT
// Fonction pour exporter le graphe en PNG
function exportGraphToPng() {
const combinedSvg = document.getElementById("combinedSvg");
html2canvas(combinedSvg).then(function(canvas) {
// Convertir le canvas en image
const imgData = canvas.toDataURL("image/png");
// Convertir l'image en un objet Blob
const blob = dataURLtoBlob(imgData);
// Utiliser la librairie FileSaver.js pour déclencher le téléchargement
saveAs(blob, "graph.png");
});
}
// Attendez que le contenu de la page soit chargé
document.addEventListener('DOMContentLoaded', function() {
// Associer l'événement de clic au bouton pour déclencher l'export
const exportButton = document.getElementById("exportButton");
exportButton.addEventListener("click", exportGraphToPng);
// ... Le reste de votre code JavaScript existant ...
});
</script>
</body>
</html>
Obrigado pela ajuda !
Se você olhar para o centro do círculo, na primeira árvore radial os galhos vão em uma "linha reta" a partir do centro. Na segunda árvore radial, as ramificações formam uma curva no início e depois vão para os nós filhos. Não consigo impedir esta curva inicial, você pode me ajudar?
You can handle first links differently for example for your paths when you draw links: