javascriptd3.jstreeradiusradial

How to edit the first node radius of a D3.js RadialTree?


Hello (my english isn't that good, so the rest is translate with GoogleTrad, i'm french =D)

For a project, I am creating a radialtree with html, js code and the D3js library.

I try to reproduce the first tree, the most complete. The 2nd is what I'm getting so far. first tree that i'm trying te reproduce 2nd tree, my work

If you look at the center of the circle, in the first radialtree the branches go in a "straight line" from the center. In the second radialtree, the branches form a curve at the start, then go to the child nodes. I can't thwart this starting turn, can you help me?

Here is my full code:

See snippet

// 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 ...
});
   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;
        }
<!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>
     
    </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>


    </script>
</body>
</html>

Thanks for your help !

If you look at the center of the circle, in the first radialtree the branches go in a "straight line" from the center. In the second radialtree, the branches form a curve at the start, then go to the child nodes. I can't thwart this starting turn, can you help me?


Solution

  • You can handle first links differently for example for your paths when you draw links:

        .attr("d",(d)=>{
            if (d.source.depth === 0) {
                // strait line for first link
                return d3.linkRadial()
                    .angle(0)
                    .radius(function (d) {
                        return d.y;
                    })(d)
    
            } else {
                return d3.linkRadial()
                    .angle(function (d, i) {
                        return d.x;
                    })
                    .radius(function (d) {
                        return d.y;
                    })(d)
            }
            }
        )
        .attr('transform',(d)=>{
             if(d.target.depth<= 1 && d.source.depth===0) {
                 // rotate first line only
                 return`rotate(${d.target.x*(180/Math.PI)})`;
             }
             return ''
        });
    

    // 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", (d) => {
          if (d.source.depth === 0) {
            // strait line for first link
            return d3.linkRadial()
              .angle(0)
              .radius(function(d) {
                return d.y;
              })(d)
    
          } else {
            return d3.linkRadial()
              .angle(function(d, i) {
                return d.x;
              })
              .radius(function(d) {
                return d.y;
              })(d)
          }
        })
        .attr('transform', (d) => {
          if (d.target.depth <= 1 && d.source.depth === 0) {
            // rotate first line only
            return `rotate(${d.target.x*(180/Math.PI)})`;
          }
          return ''
        });
    
      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 ...
    });
    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;
    }
    <!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>
    
      </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>
      </script>
    </body>
    
    </html>