import { useTheme } from '@mui/material'; import { Group } from '@visx/group'; import { Pie } from '@visx/shape'; import { Text } from '@visx/text'; import numeral from 'numeral'; import { always, equals, gt, ifElse, lt } from 'ramda'; import { useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Tooltip } from '../../components'; import { getValueByUnit } from '../common/utils'; import { Legend as LegendComponent } from '../Legend'; import type { LegendProps } from '../Legend/models'; import type { PieProps } from './models'; import { usePieStyles } from './PieChart.styles'; import { useResponsivePie } from './useResponsivePie'; const DefaultLegend = ({ scale, direction }: LegendProps): JSX.Element => ( ); type Placement = 'left' | 'right' | 'top' | 'bottom'; const getTooltipPlacement = ({ radianX, radianY }: { radianX: number; radianY: number; }): Placement => { if (gt(Math.abs(radianX), Math.abs(radianY))) { return ifElse<[b: number], Placement, Placement>( lt(0), always('right'), always('left') )(radianX); } return ifElse<[b: number], Placement, Placement>( lt(0), always('bottom'), always('top') )(radianY); }; const ResponsivePie = ({ title, variant = 'pie', width, height, data, unit = 'number', Legend = DefaultLegend, displayLegend = true, displayTotal = true, innerRadius: defaultInnerRadius = 40, innerRadiusNoLimit = false, onArcClick, padAngle = 0, displayValues, TooltipContent, legendDirection = 'column', tooltipProps = {}, opacity = 1 }: PieProps & { height: number; width: number }): JSX.Element => { const { t } = useTranslation(); const theme = useTheme(); const legendRef = useRef(null); const titleRef = useRef(null); const { half, legendScale, svgContainerSize, svgSize, total, innerRadius, isContainsExactlyOneNonZeroValue } = useResponsivePie({ data, defaultInnerRadius, height, innerRadiusNoLimit, legendRef, titleRef, unit, width }); const isTooSmallForLegend = lt(width, 170); const isSmall = lt(width, 130); const mustDisplayLegend = isTooSmallForLegend ? false : displayLegend; const { classes } = usePieStyles({ svgSize }); return ( {(equals(variant, 'pie') || isSmall) && title && ( {`${displayTotal ? numeral(total).format('0a').toUpperCase() : ''} `} {t(title)} )} pie chart { const iRadius = innerRadiusNoLimit ? innerRadius : half - innerRadius; return equals(variant, 'pie') ? 0 : iRadius; }} outerRadius={half} padAngle={padAngle} pieValue={(items) => items.value} > {(pie) => { return pie.arcs.map((arc) => { const [centroidX, centroidY] = pie.path.centroid(arc); const midAngle = Math.atan2(centroidY, centroidX); const labelRadius = half * 0.8; const labelX = Math.cos(midAngle) * labelRadius; const labelY = Math.sin(midAngle) * labelRadius; const angle = arc.endAngle - arc.startAngle; const minAngle = 0.2; const x = equals(variant, 'donut') ? centroidX : labelX; const y = equals(variant, 'donut') ? centroidY : labelY; const onClick = (): void => { onArcClick?.(arc.data); }; return ( ) } leaveDelay={200} placement={getTooltipPlacement({ radianX: Math.cos(midAngle), radianY: Math.sin(midAngle) })} > { // biome-ignore lint/a11y/noStaticElementInteractions: need it undefined} > {displayValues && !isContainsExactlyOneNonZeroValue && angle > minAngle && ( {getValueByUnit({ total, unit, value: arc.data.value })} )} } ); }); }} {equals(variant, 'donut') && !isSmall && title && ( <> {numeral(total).format('0a').toUpperCase()} {t(title)} > )} {mustDisplayLegend && ( )} ); }; export default ResponsivePie;