[DASHBOARD] Allow hovering small pie slices that are smaller than the gap (#17330)

Fixes https://github.com/twentyhq/core-team-issues/issues/2059

## Video QA

### Example with normal padding


https://github.com/user-attachments/assets/aa0c610a-cdac-472e-afe7-b995ef3f32cd

### Example with bigger padding to better see the behavior


https://github.com/user-attachments/assets/ff15b1f1-d18a-4fae-858a-2388f2cd9fa2
This commit is contained in:
Raphaël Bosi
2026-01-22 13:18:31 +01:00
committed by GitHub
parent 8dec37b826
commit 2cda1f5f08
2 changed files with 47 additions and 26 deletions
@@ -11,9 +11,10 @@ type CustomArcsLayerProps = Pick<
PieCustomLayerProps<PieChartDataItemWithColor>,
'dataWithArc' | 'arcGenerator' | 'centerX' | 'centerY'
> & {
onMouseMove?: MouseEventHandler<PieChartDataItemWithColor, SVGPathElement>;
onMouseLeave?: MouseEventHandler<PieChartDataItemWithColor, SVGPathElement>;
onClick?: MouseEventHandler<PieChartDataItemWithColor, SVGPathElement>;
padAngle: number;
onMouseMove?: MouseEventHandler<PieChartDataItemWithColor, SVGElement>;
onMouseLeave?: MouseEventHandler<PieChartDataItemWithColor, SVGElement>;
onClick?: MouseEventHandler<PieChartDataItemWithColor, SVGElement>;
};
export const CustomArcsLayer = ({
@@ -21,6 +22,7 @@ export const CustomArcsLayer = ({
arcGenerator,
centerX,
centerY,
padAngle,
onMouseMove,
onMouseLeave,
onClick,
@@ -29,33 +31,35 @@ export const CustomArcsLayer = ({
graphWidgetHighlightedLegendIdComponentState,
);
const { transition, interpolate } = useArcsTransition(
[...dataWithArc],
'innerRadius',
);
const { transition } = useArcsTransition([...dataWithArc], 'innerRadius');
return (
<g transform={`translate(${centerX},${centerY})`}>
{transition((style, datum) => {
{transition((_, datum) => {
const isDimmed =
isDefined(highlightedLegendId) &&
String(highlightedLegendId) !== String(datum.id);
const arcLength = datum.arc.endAngle - datum.arc.startAngle;
const padAngleRadians = (padAngle * Math.PI) / 180;
const clampedPadAngle = Math.min(
padAngleRadians,
Math.max(0, arcLength - 0.0001),
);
const halfPadAngle = clampedPadAngle / 2;
const hitAreaPath = arcGenerator(datum.arc);
const paddedArc = clampedPadAngle
? {
...datum.arc,
startAngle: datum.arc.startAngle + halfPadAngle,
endAngle: datum.arc.endAngle - halfPadAngle,
}
: datum.arc;
const visiblePath = arcGenerator(paddedArc);
return (
<animated.path
<g
key={datum.id}
d={interpolate(
style.startAngle,
style.endAngle,
style.innerRadius,
style.outerRadius,
arcGenerator,
)}
fill={datum.color}
opacity={isDimmed ? LEGEND_HIGHLIGHT_DIMMED_OPACITY : 1}
style={{
transition: 'opacity 0.15s ease-in-out',
}}
pointerEvents="all"
onMouseMove={
onMouseMove ? (event) => onMouseMove(datum, event) : undefined
}
@@ -63,7 +67,17 @@ export const CustomArcsLayer = ({
onMouseLeave ? (event) => onMouseLeave(datum, event) : undefined
}
onClick={onClick ? (event) => onClick(datum, event) : undefined}
/>
>
<animated.path d={hitAreaPath ?? undefined} fill="transparent" />
<animated.path
d={visiblePath ?? undefined}
fill={datum.color}
opacity={isDimmed ? LEGEND_HIGHLIGHT_DIMMED_OPACITY : 1}
style={{
transition: 'opacity 0.15s ease-in-out',
}}
/>
</g>
);
})}
</g>
@@ -111,7 +111,7 @@ export const GraphWidgetPieChart = ({
const handleSliceMove = useCallback(
(
datum: ComputedDatum<PieChartDataItemWithColor>,
event: ReactMouseEvent<SVGPathElement>,
event: ReactMouseEvent<SVGElement>,
) => {
if (!isDefined(containerRef.current)) return;
@@ -140,14 +140,16 @@ export const GraphWidgetPieChart = ({
const chartColors = hasNoData
? [theme.background.tertiary]
: enrichedData.map((item) => item.colorScheme.solid);
const pieChartPadAngle = hasNoData ? 0 : 0.4;
const ArcsLayer = useCallback(
(props: PieCustomLayerProps<PieChartDataItem>) => (
(props: PieCustomLayerProps<PieChartDataItemWithColor>) => (
<CustomArcsLayer
dataWithArc={props.dataWithArc}
arcGenerator={props.arcGenerator}
centerX={props.centerX}
centerY={props.centerY}
padAngle={pieChartPadAngle}
onMouseMove={hasNoData ? undefined : handleSliceMove}
onMouseLeave={hasNoData ? undefined : handleSliceLeave}
onClick={
@@ -161,7 +163,13 @@ export const GraphWidgetPieChart = ({
}
/>
),
[hasNoData, handleSliceMove, handleSliceLeave, onSliceClick],
[
hasNoData,
handleSliceMove,
handleSliceLeave,
onSliceClick,
pieChartPadAngle,
],
);
return (
@@ -177,7 +185,6 @@ export const GraphWidgetPieChart = ({
data={chartData}
margin={showDataLabels && !hasNoData ? PIE_CHART_MARGINS : {}}
innerRadius={0.8}
padAngle={hasNoData ? 0 : 0.4}
colors={chartColors}
enableArcLinkLabels={showDataLabels && !hasNoData}
enableArcLabels={false}