Skip to content

Commit 743d6d8

Browse files
authored
feat(PieChart / DonutChart): add option to mark chart segments as active (#667)
1 parent eb6aa69 commit 743d6d8

File tree

4 files changed

+108
-5
lines changed

4 files changed

+108
-5
lines changed

.reuse/dep5

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ Files:
77
*
88
Copyright: 2019-2020 SAP SE or an SAP affiliate company and UI5 Web Components for React contributors
99
License: Apache-2.0
10+
11+
Files: /packages/charts/src/components/PieChart/PieChart.tsx
12+
Copyright: 2016-2020 Recharts Group
13+
License: MIT

packages/charts/src/components/DonutChart/DonutChart.stories.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,19 @@ export default {
1717
},
1818
dimension: {
1919
type: null
20+
},
21+
showActiveSegmentDataLabel: {
22+
type: 'boolean'
23+
},
24+
activeSegment: {
25+
type: 'number'
2026
}
2127
},
2228
args: {
2329
innerRadius: '20%',
24-
outerRadius: '90%'
30+
outerRadius: '90%',
31+
activeSegment: 9,
32+
showActiveSegmentDataLabel: true
2533
}
2634
};
2735

@@ -35,6 +43,10 @@ export const renderStory = (props) => {
3543
onLegendClick={props.onLegendClick}
3644
style={{ width: '50%' }}
3745
dataset={simpleDataSet}
46+
chartConfig={{
47+
activeSegment: props.activeSegment,
48+
showActiveSegmentDataLabel: props.showActiveSegmentDataLabel
49+
}}
3850
dimension={{
3951
accessor: 'name'
4052
}}

packages/charts/src/components/PieChart/PieChart.tsx

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ChartContainer } from '@ui5/webcomponents-react-charts/lib/components/C
44
import { PieChartPlaceholder } from '@ui5/webcomponents-react-charts/lib/PieChartPlaceholder';
55
import { useLegendItemClick } from '@ui5/webcomponents-react-charts/lib/useLegendItemClick';
66
import React, { CSSProperties, FC, forwardRef, Ref, useCallback, useMemo, isValidElement, cloneElement } from 'react';
7-
import { Cell, Label, Legend, Pie, PieChart as PieChartLib, Tooltip, Text } from 'recharts';
7+
import { Cell, Label, Legend, Pie, PieChart as PieChartLib, Tooltip, Text, Sector } from 'recharts';
88
import { getValueByDataKey } from 'recharts/lib/util/ChartUtils';
99
import { IChartBaseProps } from '../../interfaces/IChartBaseProps';
1010
import { IChartMeasure } from '../../interfaces/IChartMeasure';
@@ -85,6 +85,8 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H
8585
};
8686
}, [props.chartConfig]);
8787

88+
const showActiveSegmentDataLabel = chartConfig.showActiveSegmentDataLabel ?? true;
89+
8890
const dimension: DimensionConfig = useMemo(
8991
() => ({
9092
formatter: defaultFormatter,
@@ -103,7 +105,7 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H
103105

104106
const dataLabel = useCallback(
105107
(props) => {
106-
if (measure.hideDataLabel) return null;
108+
if (measure.hideDataLabel || chartConfig.activeSegment === props.index) return null;
107109

108110
if (isValidElement(measure.DataLabel)) {
109111
return cloneElement(measure.DataLabel, { ...props, config: measure });
@@ -115,7 +117,7 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H
115117
</Text>
116118
);
117119
},
118-
[measure]
120+
[measure, chartConfig.activeSegment]
119121
);
120122

121123
const tooltipValueFormatter = useCallback((value, name) => [measure.formatter(value), dimension.formatter(name)], [
@@ -143,6 +145,85 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H
143145
[onDataPointClick]
144146
);
145147

148+
const renderActiveShape = useCallback(
149+
(props) => {
150+
const RADIAN = Math.PI / 180;
151+
const { cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, fill, payload, percent, value } = props;
152+
const sin = Math.sin(-RADIAN * midAngle);
153+
const cos = Math.cos(-RADIAN * midAngle);
154+
const sx = cx + (outerRadius + 10) * cos;
155+
const sy = cy + (outerRadius + 10) * sin;
156+
const mx = cx + (outerRadius + 30) * cos;
157+
const my = cy + (outerRadius + 30) * sin;
158+
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
159+
const ey = my;
160+
const textAnchor = cos >= 0 ? 'start' : 'end';
161+
162+
return (
163+
<g>
164+
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>
165+
{payload.name}
166+
</text>
167+
<Sector
168+
cx={cx}
169+
cy={cy}
170+
innerRadius={innerRadius}
171+
outerRadius={outerRadius}
172+
startAngle={startAngle}
173+
endAngle={endAngle}
174+
fill={fill}
175+
/>
176+
<Sector
177+
cx={cx}
178+
cy={cy}
179+
startAngle={startAngle}
180+
endAngle={endAngle}
181+
innerRadius={outerRadius + 6}
182+
outerRadius={outerRadius + 10}
183+
fill={fill}
184+
/>
185+
{showActiveSegmentDataLabel && (
186+
<>
187+
<path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none" />
188+
<circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
189+
<text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} textAnchor={textAnchor} fill={fill}>
190+
{measure.formatter(value)}
191+
</text>
192+
<text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} dy={18} textAnchor={textAnchor} fill={fill}>
193+
{`(${(percent * 100).toFixed(2)}%)`}
194+
</text>
195+
</>
196+
)}
197+
</g>
198+
);
199+
},
200+
[showActiveSegmentDataLabel]
201+
);
202+
203+
const renderLabelLine = useCallback(
204+
(props) => {
205+
if (!measure.hideDataLabel || chartConfig.activeSegment === props.index) return null;
206+
return Pie.renderLabelLineItem(undefined, props);
207+
},
208+
[chartConfig.activeSegment]
209+
);
210+
211+
const legendWrapperStyle = useMemo(() => {
212+
if (chartConfig.activeSegment != null && showActiveSegmentDataLabel) {
213+
if (chartConfig.legendPosition === 'bottom') {
214+
return {
215+
paddingTop: '30px'
216+
};
217+
} else if (chartConfig.legendPosition === 'top') {
218+
return {
219+
paddingBottom: '30px'
220+
};
221+
}
222+
}
223+
224+
return null;
225+
}, [showActiveSegmentDataLabel, chartConfig.activeSegment, chartConfig.legendPosition]);
226+
146227
return (
147228
<ChartContainer
148229
dataset={dataset}
@@ -172,8 +253,10 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H
172253
data={dataset}
173254
animationBegin={0}
174255
isAnimationActive={noAnimation === false}
175-
labelLine={measure.hideDataLabel !== true}
256+
labelLine={renderLabelLine}
176257
label={dataLabel}
258+
activeIndex={chartConfig.activeSegment}
259+
activeShape={chartConfig.activeSegment != null && renderActiveShape}
177260
>
178261
{centerLabel && <Label position={'center'}>{centerLabel}</Label>}
179262
{dataset &&
@@ -199,6 +282,7 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H
199282
verticalAlign={chartConfig.legendPosition}
200283
align={chartConfig.legendHorizontalAlign}
201284
onClick={onItemLegendClick}
285+
wrapperStyle={legendWrapperStyle}
202286
/>
203287
)}
204288
</PieChartLib>

packages/charts/src/interfaces/IPolarChartConfig.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ export interface IPolarChartConfig {
1010

1111
tooltipItemStyle?: CSSProperties;
1212
tooltipLabelStyle?: CSSProperties;
13+
14+
activeSegment?: number;
15+
showActiveSegmentDataLabel?: boolean;
1316
}

0 commit comments

Comments
 (0)