Skip to content

Commit 3cca3b0

Browse files
authored
Add support for customizable radius per sector in Pie Chart (#5244)
1 parent 9557a6e commit 3cca3b0

File tree

5 files changed

+127
-13
lines changed

5 files changed

+127
-13
lines changed

src/polar/Pie.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ interface PieDef {
5959
/** The inner radius of sectors */
6060
innerRadius?: number | string;
6161
/** The outer radius of sectors */
62-
outerRadius?: number | string;
62+
outerRadius?: number | string | ((dataPoint: any) => number);
6363
cornerRadius?: number | string;
6464
}
6565

@@ -340,22 +340,36 @@ const getTextAnchor = (x: number, cx: number) => {
340340
return 'middle';
341341
};
342342

343+
const getOuterRadius = (
344+
dataPoint: any,
345+
outerRadius?: number | string | ((element: any) => number),
346+
maxPieRadius?: number,
347+
) => {
348+
if (typeof outerRadius === 'function') {
349+
return outerRadius(dataPoint);
350+
}
351+
return getPercentValue(outerRadius, maxPieRadius, maxPieRadius * 0.8);
352+
};
353+
343354
const parseCoordinateOfPie = (
344355
item: {
345356
cx?: number | string;
346357
cy?: number | string;
347358
innerRadius?: number | string;
348-
outerRadius?: number | string;
359+
outerRadius?: number | string | ((dataPoint: any) => number);
349360
maxRadius?: number;
350361
},
351362
offset: ChartOffset,
363+
dataPoint: any,
352364
): PieCoordinate => {
353365
const { top, left, width, height } = offset;
354366
const maxPieRadius = getMaxRadius(width, height);
355367
const cx = left + getPercentValue(item.cx, width, width / 2);
356368
const cy = top + getPercentValue(item.cy, height, height / 2);
357369
const innerRadius = getPercentValue(item.innerRadius, maxPieRadius, 0);
358-
const outerRadius = getPercentValue(item.outerRadius, maxPieRadius, maxPieRadius * 0.8);
370+
371+
const outerRadius = getOuterRadius(dataPoint, item.outerRadius, maxPieRadius);
372+
359373
const maxRadius = item.maxRadius || Math.sqrt(width * width + height * height) / 2;
360374

361375
return { cx, cy, innerRadius, outerRadius, maxRadius };
@@ -423,15 +437,14 @@ export function computePieSectors({
423437
paddingAngle?: number;
424438
minAngle?: number;
425439
innerRadius?: number | string;
426-
outerRadius?: number | string;
440+
outerRadius?: number | string | ((dataPoint: any) => number);
427441
cornerRadius?: number | string;
428442
presentationProps?: Record<string, string>;
429443
};
430444
offset: ChartOffset;
431-
}): { sectors: ReadonlyArray<PieSectorDataItem>; coordinate: PieCoordinate } {
445+
}): { sectors: ReadonlyArray<PieSectorDataItem> } {
432446
const { cornerRadius, startAngle, endAngle, dataKey, nameKey, tooltipType } = pieSettings;
433447
const minAngle = Math.abs(pieSettings.minAngle);
434-
const coordinate = parseCoordinateOfPie(pieSettings, offset);
435448
const deltaAngle = parseDeltaAngle(startAngle, endAngle);
436449
const absDeltaAngle = Math.abs(deltaAngle);
437450
const paddingAngle = displayedData.length <= 1 ? 0 : (pieSettings.paddingAngle ?? 0);
@@ -451,6 +464,7 @@ export function computePieSectors({
451464
sectors = displayedData.map((entry: any, i: number) => {
452465
const val = getValueByDataKey(entry, dataKey, 0);
453466
const name = getValueByDataKey(entry, nameKey, i);
467+
const coordinate = parseCoordinateOfPie(pieSettings, offset, entry);
454468
const percent = (isNumber(val) ? val : 0) / sum;
455469
let tempStartAngle;
456470

@@ -497,12 +511,10 @@ export function computePieSectors({
497511
payload: entryWithCellInfo,
498512
paddingAngle: mathSign(deltaAngle) * paddingAngle,
499513
};
500-
501514
return prev;
502515
});
503516
}
504-
505-
return { sectors, coordinate };
517+
return { sectors };
506518
}
507519

508520
export class PieWithState extends PureComponent<InternalProps, State> {

src/state/selectors/pieSelectors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export type ResolvedPieSettings = {
2828
paddingAngle?: number;
2929
minAngle?: number;
3030
innerRadius?: number | string;
31-
outerRadius?: number | string;
31+
outerRadius?: number | string | ((element: any) => number);
3232
cornerRadius?: number | string;
3333
presentationProps?: Record<string, string>;
3434
};

storybook/stories/API/polar/Pie.stories.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ const GeneralProps: Args = {
3333
},
3434
outerRadius: {
3535
description: `The outer radius of all the sectors. If set a percentage, the final value is
36-
obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy.`,
36+
obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy.
37+
If set a function, the function will be called to return customized radius.`,
3738
table: {
38-
type: { summary: 'percentage | number', defaultValue: '80%' },
39+
type: { summary: 'percentage | number | Function', defaultValue: '80%' },
3940
category: 'General',
4041
},
4142
},
42-
4343
startAngle: {
4444
description: 'The start angle of first sector.',
4545
table: {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from 'react';
2+
import { Args } from '@storybook/react';
3+
import { Pie, PieChart, ResponsiveContainer } from '../../../../src';
4+
5+
const data = [
6+
{ value: 'Luck', percent: 10, customRadius: 140 },
7+
{ value: 'Skill', percent: 20, customRadius: 160 },
8+
{ value: 'Concentrated power of will', percent: 15, customRadius: 150 },
9+
{ value: 'Pleasure', percent: 50, customRadius: 190 },
10+
{ value: 'Pain', percent: 50, customRadius: 190 },
11+
{ value: 'Reason to remember the name', percent: 100, customRadius: 220 },
12+
];
13+
14+
export default {
15+
component: Pie,
16+
};
17+
18+
export const PieWithStep = {
19+
render: (args: Args) => {
20+
return (
21+
<ResponsiveContainer width="100%" height={500}>
22+
<PieChart width={400} height={400}>
23+
<Pie dataKey="percent" {...args} />
24+
</PieChart>
25+
</ResponsiveContainer>
26+
);
27+
},
28+
args: {
29+
cx: '50%',
30+
cy: '50%',
31+
data,
32+
dataKey: 'percent',
33+
nameKey: 'value',
34+
fill: '#8884d8',
35+
label: true,
36+
outerRadius: (element: any) => {
37+
return element.customRadius;
38+
},
39+
},
40+
};

test/chart/PieChart.spec.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,4 +406,66 @@ describe('<PieChart />', () => {
406406
expect(container.querySelectorAll('.label-custom-className')).toHaveLength(6);
407407
expect(container.querySelectorAll('.label-line-custom-className')).toHaveLength(6);
408408
});
409+
410+
describe('PieChart sector radius rendering', () => {
411+
const assertSectorRadius = (element: Element, radius: number) => {
412+
// Checks if the 'd' attribute has an arc with the specified radius.
413+
const dAttribute = element.getAttribute('d');
414+
const arcRadius = new RegExp(`A ${radius},${radius}`);
415+
expect(dAttribute).toMatch(arcRadius);
416+
};
417+
418+
it('renders sectors with a constant radius', () => {
419+
const outerRadius = 80;
420+
const { container } = render(
421+
<PieChart width={800} height={400}>
422+
<Pie
423+
dataKey="value"
424+
isAnimationActive={false}
425+
data={[
426+
{ name: 'Group A', value: 400 },
427+
{ name: 'Group B', value: 300 },
428+
]}
429+
cx={200}
430+
cy={200}
431+
outerRadius={outerRadius}
432+
fill="#ff7300"
433+
/>
434+
</PieChart>,
435+
);
436+
437+
const elementA = container.querySelector('path[name="Group A"]');
438+
assertSectorRadius(elementA, outerRadius);
439+
440+
const elementB = container.querySelector('path[name="Group B"]');
441+
assertSectorRadius(elementB, outerRadius);
442+
});
443+
444+
it('renders sectors with radius based on outerRadius function', () => {
445+
const { container } = render(
446+
<PieChart width={800} height={400}>
447+
<Pie
448+
dataKey="value"
449+
isAnimationActive={false}
450+
data={[
451+
{ name: 'Group A', value: 400 },
452+
{ name: 'Group B', value: 300 },
453+
]}
454+
cx={200}
455+
cy={200}
456+
outerRadius={(element: any) => {
457+
return element.value / 10;
458+
}}
459+
fill="#ff7300"
460+
/>
461+
</PieChart>,
462+
);
463+
464+
const elementA = container.querySelector('path[name="Group A"]');
465+
assertSectorRadius(elementA, 40);
466+
467+
const elementB = container.querySelector('path[name="Group B"]');
468+
assertSectorRadius(elementB, 30);
469+
});
470+
});
409471
});

0 commit comments

Comments
 (0)