Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DonutChart): add option to mark chart segments as active #667

Merged
merged 3 commits into from
Aug 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ Files:
*
Copyright: 2019-2020 SAP SE or an SAP affiliate company and UI5 Web Components for React contributors
License: Apache-2.0

Files: /packages/charts/src/components/PieChart/PieChart.tsx
Copyright: 2016-2020 Recharts Group
License: MIT
14 changes: 13 additions & 1 deletion packages/charts/src/components/DonutChart/DonutChart.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ export default {
},
dimension: {
type: null
},
showActiveSegmentDataLabel: {
type: 'boolean'
},
activeSegment: {
type: 'number'
}
},
args: {
innerRadius: '20%',
outerRadius: '90%'
outerRadius: '90%',
activeSegment: 9,
showActiveSegmentDataLabel: true
}
};

Expand All @@ -35,6 +43,10 @@ export const renderStory = (props) => {
onLegendClick={props.onLegendClick}
style={{ width: '50%' }}
dataset={simpleDataSet}
chartConfig={{
activeSegment: props.activeSegment,
showActiveSegmentDataLabel: props.showActiveSegmentDataLabel
}}
dimension={{
accessor: 'name'
}}
Expand Down
92 changes: 88 additions & 4 deletions packages/charts/src/components/PieChart/PieChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ChartContainer } from '@ui5/webcomponents-react-charts/lib/components/C
import { PieChartPlaceholder } from '@ui5/webcomponents-react-charts/lib/PieChartPlaceholder';
import { useLegendItemClick } from '@ui5/webcomponents-react-charts/lib/useLegendItemClick';
import React, { CSSProperties, FC, forwardRef, Ref, useCallback, useMemo, isValidElement, cloneElement } from 'react';
import { Cell, Label, Legend, Pie, PieChart as PieChartLib, Tooltip, Text } from 'recharts';
import { Cell, Label, Legend, Pie, PieChart as PieChartLib, Tooltip, Text, Sector } from 'recharts';
import { getValueByDataKey } from 'recharts/lib/util/ChartUtils';
import { IChartBaseProps } from '../../interfaces/IChartBaseProps';
import { IChartMeasure } from '../../interfaces/IChartMeasure';
Expand Down Expand Up @@ -85,6 +85,8 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H
};
}, [props.chartConfig]);

const showActiveSegmentDataLabel = chartConfig.showActiveSegmentDataLabel ?? true;

const dimension: DimensionConfig = useMemo(
() => ({
formatter: defaultFormatter,
Expand All @@ -103,7 +105,7 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H

const dataLabel = useCallback(
(props) => {
if (measure.hideDataLabel) return null;
if (measure.hideDataLabel || chartConfig.activeSegment === props.index) return null;

if (isValidElement(measure.DataLabel)) {
return cloneElement(measure.DataLabel, { ...props, config: measure });
Expand All @@ -115,7 +117,7 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H
</Text>
);
},
[measure]
[measure, chartConfig.activeSegment]
);

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

const renderActiveShape = useCallback(
(props) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code is copied from recharts, right? we have to add the corresponding license entry to the .reuse/dep5 file for this file

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compare e.g with the web components project: https://github.com/SAP/ui5-webcomponents/blob/master/.reuse/dep5

const RADIAN = Math.PI / 180;
const { cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, fill, payload, percent, value } = props;
const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos;
const sy = cy + (outerRadius + 10) * sin;
const mx = cx + (outerRadius + 30) * cos;
const my = cy + (outerRadius + 30) * sin;
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
const ey = my;
const textAnchor = cos >= 0 ? 'start' : 'end';

return (
<g>
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>
{payload.name}
</text>
<Sector
cx={cx}
cy={cy}
innerRadius={innerRadius}
outerRadius={outerRadius}
startAngle={startAngle}
endAngle={endAngle}
fill={fill}
/>
<Sector
cx={cx}
cy={cy}
startAngle={startAngle}
endAngle={endAngle}
innerRadius={outerRadius + 6}
outerRadius={outerRadius + 10}
fill={fill}
/>
{showActiveSegmentDataLabel && (
<>
<path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none" />
<circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
<text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} textAnchor={textAnchor} fill={fill}>
{measure.formatter(value)}
</text>
<text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} dy={18} textAnchor={textAnchor} fill={fill}>
{`(${(percent * 100).toFixed(2)}%)`}
</text>
</>
)}
</g>
);
},
[showActiveSegmentDataLabel]
);

const renderLabelLine = useCallback(
(props) => {
if (!measure.hideDataLabel || chartConfig.activeSegment === props.index) return null;
return Pie.renderLabelLineItem(undefined, props);
},
[chartConfig.activeSegment]
);

const legendWrapperStyle = useMemo(() => {
if (chartConfig.activeSegment != null && showActiveSegmentDataLabel) {
if (chartConfig.legendPosition === 'bottom') {
return {
paddingTop: '30px'
};
} else if (chartConfig.legendPosition === 'top') {
return {
paddingBottom: '30px'
};
}
}

return null;
}, [showActiveSegmentDataLabel, chartConfig.activeSegment, chartConfig.legendPosition]);

return (
<ChartContainer
dataset={dataset}
Expand Down Expand Up @@ -172,8 +253,10 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H
data={dataset}
animationBegin={0}
isAnimationActive={noAnimation === false}
labelLine={measure.hideDataLabel !== true}
labelLine={renderLabelLine}
label={dataLabel}
activeIndex={chartConfig.activeSegment}
activeShape={chartConfig.activeSegment != null && renderActiveShape}
>
{centerLabel && <Label position={'center'}>{centerLabel}</Label>}
{dataset &&
Expand All @@ -199,6 +282,7 @@ const PieChart: FC<PieChartProps> = forwardRef((props: PieChartProps, ref: Ref<H
verticalAlign={chartConfig.legendPosition}
align={chartConfig.legendHorizontalAlign}
onClick={onItemLegendClick}
wrapperStyle={legendWrapperStyle}
/>
)}
</PieChartLib>
Expand Down
3 changes: 3 additions & 0 deletions packages/charts/src/interfaces/IPolarChartConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ export interface IPolarChartConfig {

tooltipItemStyle?: CSSProperties;
tooltipLabelStyle?: CSSProperties;

activeSegment?: number;
showActiveSegmentDataLabel?: boolean;
}