1

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?

pernifloss
  • 446
  • 9
LoChab
  • 13
  • 3

1 Answers1

0

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>
pernifloss
  • 446
  • 9
  • Thank you very much, it works!! I take this opportunity to also ask you: I would like the text to be justified and centered vertically in relation to the node, do you know how I can do this? – LoChab Aug 17 '23 at 09:37
  • I think the easiest way is to put you `` aligned with your node, then change `wrapText` function so that it move first half of `` up and second half down, by changing `` `dy` attribute according to their position – pernifloss Aug 17 '23 at 09:53
  • ok i'll try =) thanks ! – LoChab Aug 21 '23 at 06:58