Code : personnaliser un radialtree en JavaScript

Loup - 16 août 2023 à 17:18

Bonjour,

Pour un projet, je crée un radialtree avec du code html, js et la bibliothèque D3js.

J'essaie de reproduire cet arbre :

Voici ce que j'obtiens :

SI vous observez le centre du cercle, dans le premier radialtree, les branches partent en "ligne droite" du centre. Dans le second radialtree, les branches forment une espère de courbe au départ, puis partent vers les noeuds enfants. Je n'arrive pas à contrecarrer ce virage de départ, est-ce que vous pourriez m'aider ?

Voici mon code complet :

<!DOCTYPEhtml>

<html>

<head>

<title>D3.js Radial Tree Example</title>

<scriptsrc="https://d3js.org/d3.v6.min.js"></script>

<scriptsrc="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>

<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>

<linkhref="https://fonts.googleapis.com/css2?family=Roboto%3Awght%40400%3B700&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>


 

<svgid="combinedSvg"width="1500"height="1500">

<gid="radialTreeGroup">

<!-- Contenu de l'arbre radial -->

</g>

<gid="pieChartGroup">

<!-- Contenu du graphique à secteurs -->

</g>

</svg>

<divid="exportButtonContainer">

<buttonid="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) {

return1;

                    } elseif (a.depth === b.depth) {

return1.2;

                    } else {

return2;

                    }

                });

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)

return0; // 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));

    }

elseif (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>

Merci par avance !

A voir également: