你好(我的英语不太好,所以剩下的都是用GoogleTrad翻译的,我是法语=D)
对于一个项目,我正在使用 html、js 代码和 D3js 库创建一个 Radialtree。
我尝试重现第一棵树,也是最完整的一棵。第二个是我到目前为止所得到的。 这是我尝试复制的第一棵树。
如果你看圆的中心,在第一个径向树中,分支从中心开始呈“直线”。在第二个径向树中,分支从一开始形成一条曲线,然后到达子节点。我无法阻止这个开始的转弯,你能帮我吗?
这是我的完整代码: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>
感谢您的帮助 !
如果你看圆的中心,在第一个径向树中,分支从中心开始呈“直线”。在第二个径向树中,分支从一开始形成一条曲线,然后到达子节点。我无法阻止这个开始的转弯,你能帮我吗?
You can handle first links differently for example for your paths when you draw links: