diff --git a/packages/@mantine/charts/src/DonutChart/DonutChart.story.tsx b/packages/@mantine/charts/src/DonutChart/DonutChart.story.tsx index 38d6142246..5abfb0b909 100644 --- a/packages/@mantine/charts/src/DonutChart/DonutChart.story.tsx +++ b/packages/@mantine/charts/src/DonutChart/DonutChart.story.tsx @@ -12,7 +12,13 @@ const data = [ export function Usage() { return (
- +
); } diff --git a/packages/@mantine/charts/src/DonutChart/DonutChart.tsx b/packages/@mantine/charts/src/DonutChart/DonutChart.tsx index f0ec6a2316..7cc7e53e14 100644 --- a/packages/@mantine/charts/src/DonutChart/DonutChart.tsx +++ b/packages/@mantine/charts/src/DonutChart/DonutChart.tsx @@ -1,5 +1,7 @@ +import React, { useState } from 'react'; import { Cell, + Legend, Pie, PieLabel, PieProps, @@ -75,9 +77,12 @@ export interface DonutChartProps /** Controls thickness of the chart segments, `20` by default */ thickness?: number; - /** Controls chart width and height, height is increased by 40 if `withLabels` prop is set. Cannot be less than `thickness`. `80` by default */ + /** Controls chart area size along with side legend */ size?: number; + /** Controls chart width and height, height is increased by 40 if `withLabels` prop is set. Cannot be less than `thickness`. `80` by default */ + pieSize?: number; + /** Controls width of segments stroke, `1` by default */ strokeWidth?: number; @@ -104,6 +109,20 @@ export interface DonutChartProps /** A function to format values inside the tooltip */ valueFormatter?: (value: number) => string; + + /** Defines the behavior of the legend. Can be 'hover' (hover to highlight segments) or 'side' (static side legend). */ + legendMode?: 'hover' | 'side'; + + /** Specifies the position of the legend on the chart. */ + legendOrientation?: + | 'top' + | 'bottom' + | 'top-left' + | 'top-right' + | 'bottom-left' + | 'bottom-right' + | 'center-left' + | 'center-right'; } export type DonutChartFactory = Factory<{ @@ -118,7 +137,8 @@ const defaultProps: Partial = { withLabelsLine: true, paddingAngle: 0, thickness: 20, - size: 160, + size: 240, + pieSize: 160, strokeWidth: 1, startAngle: 0, endAngle: 360, @@ -188,6 +208,7 @@ export const DonutChart = factory((_props, ref) => { withLabels, withLabelsLine, size, + pieSize, thickness, strokeWidth, startAngle, @@ -199,10 +220,13 @@ export const DonutChart = factory((_props, ref) => { valueFormatter, strokeColor, labelsType, + legendMode, + legendOrientation, ...others } = props; const theme = useMantineTheme(); + const [hoveredSegment, setHoveredSegment] = useState(null); const getStyles = useStyles({ name: 'DonutChart', @@ -229,17 +253,37 @@ export const DonutChart = factory((_props, ref) => { fill={getThemeColor(item.color, theme)} stroke="var(--chart-stroke-color, var(--mantine-color-body))" strokeWidth={strokeWidth} + fillOpacity={ + legendMode === 'side' && hoveredSegment !== null + ? hoveredSegment === item.name + ? 1 + : 0.5 + : 1 + } /> )); + const legendPosition = { + top: { x: 0, y: -180 }, + bottom: { x: 0, y: 180 }, + 'top-left': { x: -180, y: -180 }, + 'top-right': { x: 180, y: -180 }, + 'bottom-left': { x: -180, y: 180 }, + 'bottom-right': { x: 180, y: 180 }, + 'center-left': { x: -230, y: 0 }, + 'center-right': { x: 230, y: 0 }, + }; + + const legendOffset = { x: 260, y: legendPosition[legendOrientation || 'center-right'].y }; + return ( ((_props, ref) => { )} - {withTooltip && ( + {legendMode === 'hover' && withTooltip && ( ((_props, ref) => { valueFormatter={valueFormatter} /> )} + position={{ x: legendOffset.x, y: legendOffset.y }} {...tooltipProps} /> )} + {legendMode === 'side' && ( + setHoveredSegment(e?.value as string)} + onMouseLeave={() => setHoveredSegment(null)} + wrapperStyle={{ + position: 'absolute', + left: `${legendOffset.x}px`, + top: `${legendOffset.y}px`, + fontSize: '14px', + fontFamily: 'var(--mantine-font-family)', + whiteSpace: 'nowrap', + visibility: 'visible', + }} + /> + )} + {children}