"use client"; import React, {useEffect, useRef} from "react"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSpinner } from "@fortawesome/free-solid-svg-icons"; import * as d3 from "d3"; const defaultColorScheme = [ "#1e64af", "#419bd2", "#69beeb", "#d7b428", "#e19196", "#a5236e", "#eb1e4b", "#f06937", "#f5dc50", "#2d969b", "#335c6c", "#ff7d2d", "#fac846", "#a0c382", "#5f9b8c", "#325a69", "#fff5af", "#e1a03c", "#a0282d", "#550a0f" ]; /** * @param {object} props * @param {[{region: string, occurrences: number}]} props.data */ export default function Donut({data}) { const ref = useRef(); const width = 300; const height = 300; const padding = 20; const thicknessOnMouseOver = 10; const radius = Math.min(width, height) / 2 - padding; const deathAngle = 0.2; useEffect(() => { if (data === undefined || data.length === 0) { return; } const rawPieData = d3.pie().value((x) => x.occurrences)(data); let pieData = []; const aggregate = { data: { region: "aggregate", occurrences: 0 }, index: rawPieData.length, value: 0, padAngle: 0, startAngle: 0, endAngle: Math.PI * 2 }; let lastEndAngle = 0; for (const d of rawPieData) { if (d.endAngle > lastEndAngle) { lastEndAngle = d.endAngle; } // if d angle is less than deathAngle, we don't show it in final donut if (d.endAngle - d.startAngle < deathAngle) { aggregate.startAngle = lastEndAngle; aggregate.endAngle -= d.endAngle - d.startAngle; aggregate.value += d.value; aggregate.data.occurrences += d.data.occurrences; } else { pieData.push(d); } } pieData.push(aggregate); const arc = d3 .arc() .innerRadius(radius * 0.6) .outerRadius(radius) const svg = d3.select(ref.current) .attr("width", width) .attr("height", height) .append("g") .attr("transform", `translate(${width / 2}, ${height / 2})`); svg.selectAll("path") .data(pieData) .enter() .append("path") .attr("d", arc) .attr("fill", (x, i) => x.data.region === "aggregate" ? "#dddddd" : defaultColorScheme[i % data.length]) .on("mouseover", function (event, d) { const percentage = (d.value / aggregate.value).toFixed(2); const text = `${d.data.region}: ${percentage}%`; d3.select(this) .transition() .duration(200) .attr("stroke", this.attributes.fill.nodeValue) .attr("stroke-width", thicknessOnMouseOver); svg.append("text") .attr("class", "tooltip") .attr("text-anchor", "middle") .attr("dy",-radius) .text(text) .attr("transform", `translate(${width / 2 - 100}, ${height / 2 - 50})`); this.parentNode.appendChild(this); }) .on("mouseout", function () { d3.select(this) .transition() .attr("stroke", "none") .attr("stroke-width", 0); svg.select(".tooltip").remove(); }); }, [data]); return (