donut.jsx 3,74 ko
Newer Older
import React, { useEffect, useRef } from "react";
import * as d3 from "d3";

export default function DonutChart({ data }) {
    const ref = useRef();
    const svgWidth = 1100;
    const svgHeight = 1000;
    const width = 400;
    const height = 400;
    const radius = Math.min(width, height) / 2;
    const innerRadius = radius * 0.6;
    const outerRadius = radius * 0.9;
    const hoverOuterRadius = outerRadius + 20;
    const minPercentage = 1; // le seuil pour regrouper les petites divisions
    useEffect(() => {
        if (!data || data.length === 0) {
            return;
        }
        const totalOccurrences = data.reduce((acc, item) => acc + item.occurrences, 0);
        let otherData = { region: "Autres", occurrences: 0, details: [] };
        let newData = data.filter(d => {
            let percentage = (d.occurrences / totalOccurrences) * 100;
            if (percentage < minPercentage) {
                otherData.occurrences += d.occurrences;
                otherData.details.push(`${d.region} (${percentage.toFixed(2)}%)`);
                return false;
            }
            return true;
        });
        if (otherData.occurrences > 0) {
            newData.push(otherData);
        }
        const pieData = d3.pie().value(d => d.occurrences)(newData);
        const arc = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius);
        const hoverArc = d3.arc().innerRadius(innerRadius).outerRadius(hoverOuterRadius);
        const labelArc = d3.arc().innerRadius(outerRadius + 30).outerRadius(outerRadius + 30);
        const svg = d3.select(ref.current)
            .attr("width", svgWidth)
            .attr("height", svgHeight)
            .append("g")
            .attr("transform", `translate(${svgWidth / 2}, ${svgHeight / 2})`);
        svg.selectAll("path")
            .data(pieData)
            .enter()
            .append("path")
            .attr("d", arc)
            .attr("fill", (d, i) => d3.schemeCategory10[i % 10])
            .on("mouseover", function(event, d) {
                d3.select(this).transition().attr("d", hoverArc);
                let text = (d.data.region === "Autres") ?
                    otherData.details.join(", ") :
                    `${d.data.region}: ${((d.data.occurrences / totalOccurrences) * 100).toFixed(2)}%`;
                const centroid = hoverArc.centroid(d);
                svg.append("text")
                    .attr("class", "tooltip")
                    .attr("x", centroid[0])
                    .attr("y", centroid[1])
                    .attr("text-anchor", "middle")
                    .text(text);
            })
            .on("mouseout", function() {
                d3.select(this).transition().attr("d", arc);
                svg.selectAll(".tooltip").remove();
            });
        // l'ajout des lignes et textes pour chaque division sauf pour autres
        svg.selectAll(".label-line")
            .data(pieData.filter(d => d.data.region !== "Autres"))
            .enter()
            .append("line")
            .attr("x1", d => labelArc.centroid(d)[0])
            .attr("y1", d => labelArc.centroid(d)[1])
            .attr("x2", d => labelArc.centroid(d)[0] * 2)
            .attr("y2", d => labelArc.centroid(d)[1] * 2)
            .attr("stroke", "gray");

        svg.selectAll(".label-text")
            .data(pieData.filter(d => d.data.region !== "Autres"))
            .enter()
            .append("text")
            .attr("transform", d => {
                const [x, y] = labelArc.centroid(d);
                return `translate(${x * 2}, ${y * 2})`;
            })
            .attr("text-anchor", "middle")
            .text(d => `${d.data.region}: ${((d.data.occurrences / totalOccurrences) * 100).toFixed(2)}%`);

    return <svg ref={ref}></svg>;