import PropTypes from "prop-types"; import { PieChart as MuiPieChart } from "@mui/x-charts/PieChart"; import { useDrawingArea } from "@mui/x-charts"; import { useState } from "react"; import { useTheme } from "@emotion/react"; import { Box } from "@mui/material"; /** * Renders a centered label within a pie chart. * * @param {Object} props * @param {string | number} props.value - The value to display in the label. * @param {string} props.color - The color of the text. * @returns {JSX.Element} */ const PieCenterLabel = ({ value, color, setExpand }) => { const { width, height } = useDrawingArea(); return ( setExpand(true)} > {value} ); }; PieCenterLabel.propTypes = { value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), color: PropTypes.string, setExpand: PropTypes.func, }; /** * A component that renders a label on a pie chart slice. * The label is positioned relative to the center of the pie chart and is optionally highlighted. * * @param {Object} props * @param {number} props.value - The value to display inside the pie slice. * @param {number} props.startAngle - The starting angle of the pie slice in degrees. * @param {number} props.endAngle - The ending angle of the pie slice in degrees. * @param {string} props.color - The color of the label text when highlighted. * @param {boolean} props.highlighted - Determines if the label should be highlighted or not. * @returns {JSX.Element} */ const PieValueLabel = ({ value, startAngle, endAngle, color, highlighted }) => { const { width, height } = useDrawingArea(); // Compute the midpoint angle in radians const angle = (((startAngle + endAngle) / 2) * Math.PI) / 180; const radius = height / 4; // length from center of the circle to where the text is positioned // Calculate x and y positions const x = Math.sin(angle) * radius; const y = -Math.cos(angle) * radius; return ( +{value} ); }; // Validate props using PropTypes PieValueLabel.propTypes = { value: PropTypes.number.isRequired, startAngle: PropTypes.number.isRequired, endAngle: PropTypes.number.isRequired, color: PropTypes.string.isRequired, highlighted: PropTypes.bool.isRequired, }; /** * Weight constants for different performance metrics. * @type {Object} */ const weights = { fcp: 10, si: 10, lcp: 25, tbt: 30, cls: 25, }; const PieChart = ({ audits }) => { const theme = useTheme(); const [highlightedItem, setHighLightedItem] = useState(null); const [expand, setExpand] = useState(false); /** * Retrieves color properties based on the performance value. * * @param {number} value - The performance score used to determine the color properties. * @returns {{stroke: string, strokeBg: string, text: string, bg: string}} The color properties for the given performance value. */ const getColors = (value) => { if (value >= 90 && value <= 100) return { stroke: theme.palette.success.main, strokeBg: theme.palette.success.lowContrast, text: theme.palette.success.contrastText, bg: theme.palette.success.lowContrast, }; else if (value >= 50 && value < 90) return { stroke: theme.palette.warning.main, strokeBg: theme.palette.warning.lowContrast, text: theme.palette.warning.contrastText, bg: theme.palette.warning.lowContrast, }; else if (value >= 0 && value < 50) return { stroke: theme.palette.error.contrastText, strokeBg: theme.palette.error.lowContrast, text: theme.palette.error.contrastText, bg: theme.palette.error.lowContrast, }; return { stroke: theme.palette.tertiary.contrastText, strokeBg: theme.palette.tertiary.contrastText, text: theme.palette.tertiary.contrastText, bg: theme.palette.tertiary.main, }; }; /** * Calculates and formats the data needed for rendering a pie chart based on audit scores and weights. * This function generates properties for each pie slice, including angles, radii, and colors. * It also calculates performance based on the weighted values. * * @returns {Array} An array of objects, each representing the properties for a slice of the pie chart. * @returns {number} performance - A variable updated with the rounded sum of weighted values. */ let performance = 0; const getPieData = (audits) => { if (typeof audits === "undefined") return undefined; let data = []; let startAngle = 0; const padding = 3; // padding between arcs const max = 360 - padding * (Object.keys(audits).length - 1); // _id is a child of audits Object.keys(audits).forEach((key) => { if (audits[key].score) { let value = audits[key].score * weights[key]; let endAngle = startAngle + (weights[key] * max) / 100; let theme = getColors(audits[key].score * 100); data.push({ id: key, data: [ { value: value, color: theme.stroke, label: key.toUpperCase(), }, { value: weights[key] - value, color: theme.strokeBg, label: "", }, ], arcLabel: (item) => `${item.label}`, arcLabelRadius: 95, startAngle: startAngle, endAngle: endAngle, innerRadius: 73, outerRadius: 80, cornerRadius: 2, highlightScope: { faded: "global", highlighted: "series" }, faded: { innerRadius: 73, outerRadius: 80, additionalRadius: -20, arcLabelRadius: 5, }, cx: pieSize.width / 2, }); performance += Math.floor(value); startAngle = endAngle + padding; } }); return data; }; const pieSize = { width: 230, height: 230 }; const pieData = getPieData(audits); const colorMap = getColors(performance); return ( setExpand(false)}> {expand ? ( {pieData?.map((pie) => ( ))} ) : ( )} ); }; PieChart.propTypes = { audits: PropTypes.object, }; export default PieChart;