diff --git a/change/@fluentui-react-charting-d99f14e6-dd3a-4f33-a8b3-9e28ea5bcfab.json b/change/@fluentui-react-charting-d99f14e6-dd3a-4f33-a8b3-9e28ea5bcfab.json new file mode 100644 index 00000000000000..a1be1b61112df7 --- /dev/null +++ b/change/@fluentui-react-charting-d99f14e6-dd3a-4f33-a8b3-9e28ea5bcfab.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Enabling multiple legend selection for Grouped Vertical Bar Chart", + "packageName": "@fluentui/react-charting", + "email": "120183316+srmukher@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/charts/react-charting/src/components/GroupedVerticalBarChart/GroupedVerticalBarChart.base.tsx b/packages/charts/react-charting/src/components/GroupedVerticalBarChart/GroupedVerticalBarChart.base.tsx index 3e3daa0f0731e5..b11a1b2e1c5a35 100644 --- a/packages/charts/react-charting/src/components/GroupedVerticalBarChart/GroupedVerticalBarChart.base.tsx +++ b/packages/charts/react-charting/src/components/GroupedVerticalBarChart/GroupedVerticalBarChart.base.tsx @@ -73,6 +73,7 @@ export interface IGroupedVerticalBarChartState extends IBasestate { dataPointCalloutProps?: IGVBarChartSeriesPoint; callOutAccessibilityData?: IAccessibilityProps; calloutLegend: string; + selectedLegends: string[]; } export class GroupedVerticalBarChartBase @@ -121,7 +122,7 @@ export class GroupedVerticalBarChartBase dataForHoverCard: 0, isCalloutVisible: false, refSelected: null, - selectedLegend: props.legendProps?.selectedLegend ?? '', + selectedLegends: props.legendProps?.selectedLegends || [], xCalloutValue: '', yCalloutValue: '', YValueHover: [], @@ -325,7 +326,7 @@ export class GroupedVerticalBarChartBase this.setState({ refSelected: mouseEvent, /** Show the callout if highlighted bar is hovered and Hide it if unhighlighted bar is hovered */ - isCalloutVisible: this.state.selectedLegend === '' || this.state.selectedLegend === pointData.legend, + isCalloutVisible: this._noLegendHighlighted() || this._legendHighlighted(pointData.legend), calloutLegend: pointData.legend, dataForHoverCard: pointData.data, color: pointData.color, @@ -361,7 +362,7 @@ export class GroupedVerticalBarChartBase this.setState({ refSelected: obj.refElement, /** Show the callout if highlighted bar is focused and Hide it if unhighlighted bar is focused */ - isCalloutVisible: this.state.selectedLegend === '' || this.state.selectedLegend === pointData.legend, + isCalloutVisible: this._noLegendHighlighted() || this._legendHighlighted(pointData.legend), calloutLegend: pointData.legend, dataForHoverCard: pointData.data, color: pointData.color, @@ -573,18 +574,6 @@ export class GroupedVerticalBarChartBase }); }; - private _onLegendClick(legendTitle: string): void { - if (this.state.selectedLegend === legendTitle) { - this.setState({ - selectedLegend: '', - }); - } else { - this.setState({ - selectedLegend: legendTitle, - }); - } - } - private _onLegendHover(legendTitle: string): void { this.setState({ activeLegend: legendTitle, @@ -616,9 +605,6 @@ export class GroupedVerticalBarChartBase const legend: ILegend = { title: point.legend, color, - action: () => { - this._onLegendClick(point.legend); - }, hoverAction: () => { this._handleChartMouseLeave(); this._onLegendHover(point.legend); @@ -638,10 +624,26 @@ export class GroupedVerticalBarChartBase enabledWrapLines={this.props.enabledLegendsWrapLines} focusZonePropsInHoverCard={this.props.focusZonePropsForLegendsInHoverCard} {...this.props.legendProps} + onChange={this._onLegendSelectionChange.bind(this)} /> ); }; + private _onLegendSelectionChange( + selectedLegends: string[], + event: React.MouseEvent, + currentLegend?: ILegend, + ): void { + if (this.props.legendProps?.canSelectMultipleLegends) { + this.setState({ selectedLegends }); + } else { + this.setState({ selectedLegends: selectedLegends.slice(-1) }); + } + if (this.props.legendProps?.onChange) { + this.props.legendProps.onChange(selectedLegends, event, currentLegend); + } + } + private _getAxisData = (yAxisData: IAxisData) => { if (yAxisData && yAxisData.yAxisDomainValues.length) { const { yAxisDomainValues: domainValue } = yAxisData; @@ -656,19 +658,24 @@ export class GroupedVerticalBarChartBase * 2. hovering: if there is no selected legend and the user hovers over it */ private _legendHighlighted = (legendTitle: string) => { - return ( - this.state.selectedLegend === legendTitle || - (this.state.selectedLegend === '' && this.state.activeLegend === legendTitle) - ); + return this._getHighlightedLegend().includes(legendTitle!); }; /** * This function checks if none of the legends is selected or hovered. */ private _noLegendHighlighted = () => { - return this.state.selectedLegend === '' && this.state.activeLegend === ''; + return this._getHighlightedLegend().length === 0; }; + private _getHighlightedLegend() { + return this.state.selectedLegends.length > 0 + ? this.state.selectedLegends + : this.state.activeLegend + ? [this.state.activeLegend] + : []; + } + private _getAriaLabel = (point: IGVBarChartSeriesPoint, xAxisPoint: string): string => { const xValue = point.xAxisCalloutData || xAxisPoint; const legend = point.legend; diff --git a/packages/charts/react-charting/src/components/GroupedVerticalBarChart/GroupedVerticalBarChartRTL.test.tsx b/packages/charts/react-charting/src/components/GroupedVerticalBarChart/GroupedVerticalBarChartRTL.test.tsx index 5797d95beeb161..b6ed4f6f7ef092 100644 --- a/packages/charts/react-charting/src/components/GroupedVerticalBarChart/GroupedVerticalBarChartRTL.test.tsx +++ b/packages/charts/react-charting/src/components/GroupedVerticalBarChart/GroupedVerticalBarChartRTL.test.tsx @@ -139,6 +139,206 @@ const chartPoints = [ }, ]; +const dataGVBC = [ + { + name: 'Jan - Mar', + series: [ + { + key: 'series1', + data: 33000, + color: DefaultPalette.blue, + legend: '2022', + xAxisCalloutData: '2022/04/30', + yAxisCalloutData: '29%', + callOutAccessibilityData: { + ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 1 of 2 2022, x value 2022/04/30, y value 29%', + }, + }, + { + key: 'series2', + data: 44000, + color: DefaultPalette.green, + legend: '2023', + xAxisCalloutData: '2023/04/30', + yAxisCalloutData: '44%', + callOutAccessibilityData: { + ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 2 of 2 2023, x value 2023/04/30, y value 44%', + }, + }, + { + key: 'series3', + data: 54000, + color: DefaultPalette.red, + legend: '2024', + xAxisCalloutData: '2024/04/30', + yAxisCalloutData: '44%', + callOutAccessibilityData: { + ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 3 of 4 2022, x value 2024/04/30, y value 44%', + }, + }, + { + key: 'series4', + data: 24000, + color: DefaultPalette.yellow, + legend: '2021', + xAxisCalloutData: '2021/04/30', + yAxisCalloutData: '44%', + callOutAccessibilityData: { + ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 4 of 4 2021, x value 2021/04/30, y value 44%', + }, + }, + ], + }, + { + name: 'Apr - Jun', + series: [ + { + key: 'series1', + data: 33000, + color: DefaultPalette.blue, + legend: '2022', + xAxisCalloutData: '2022/05/30', + yAxisCalloutData: '29%', + callOutAccessibilityData: { + ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 1 of 2 2022, x value 2022/05/30, y value 29%', + }, + }, + { + key: 'series2', + data: 3000, + color: DefaultPalette.green, + legend: '2023', + xAxisCalloutData: '2023/05/30', + yAxisCalloutData: '3%', + callOutAccessibilityData: { + ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 2 of 2 2023, x value 2023/05/30, y value 3%', + }, + }, + { + key: 'series3', + data: 9000, + color: DefaultPalette.red, + legend: '2024', + xAxisCalloutData: '2024/05/30', + yAxisCalloutData: '3%', + callOutAccessibilityData: { + ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 3 of 4 2024, x value 2024/05/30, y value 3%', + }, + }, + { + key: 'series4', + data: 12000, + color: DefaultPalette.yellow, + legend: '2021', + xAxisCalloutData: '2021/05/30', + yAxisCalloutData: '3%', + callOutAccessibilityData: { + ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 4 of 4 2021, x value 2021/05/30, y value 3%', + }, + }, + ], + }, + + { + name: 'Jul - Sep', + series: [ + { + key: 'series1', + data: 14000, + color: DefaultPalette.blue, + legend: '2022', + xAxisCalloutData: '2022/06/30', + yAxisCalloutData: '13%', + callOutAccessibilityData: { + ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 1 of 2 2022, x value 2022/06/30, y value 13%', + }, + }, + { + key: 'series2', + data: 50000, + color: DefaultPalette.green, + legend: '2023', + xAxisCalloutData: '2023/06/30', + yAxisCalloutData: '50%', + callOutAccessibilityData: { + ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 2 of 2 2023, x value 2023/06/30, y value 50%', + }, + }, + { + key: 'series3', + data: 60000, + color: DefaultPalette.red, + legend: '2024', + xAxisCalloutData: '2024/06/30', + yAxisCalloutData: '50%', + callOutAccessibilityData: { + ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 3 of 4 2024, x value 2024/06/30, y value 50%', + }, + }, + { + key: 'series4', + data: 10000, + color: DefaultPalette.yellow, + legend: '2021', + xAxisCalloutData: '2021/06/30', + yAxisCalloutData: '50%', + callOutAccessibilityData: { + ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 4 of 4 2021, x value 2021/06/30, y value 50%', + }, + }, + ], + }, + { + name: 'Oct - Dec', + series: [ + { + key: 'series1', + data: 33000, + color: DefaultPalette.blue, + legend: '2022', + xAxisCalloutData: '2022/07/30', + yAxisCalloutData: '29%', + callOutAccessibilityData: { + ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 1 of 2 2022, x value 2022/07/30, y value 29%', + }, + }, + { + key: 'series2', + data: 3000, + color: DefaultPalette.green, + legend: '2023', + xAxisCalloutData: '2023/07/30', + yAxisCalloutData: '3%', + callOutAccessibilityData: { + ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 2 of 2 2023, x value 2023/07/30, y value 3%', + }, + }, + { + key: 'series3', + data: 6000, + color: DefaultPalette.red, + legend: '2024', + xAxisCalloutData: '2024/07/30', + yAxisCalloutData: '3%', + callOutAccessibilityData: { + ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 3 of 4 2024, x value 2024/07/30, y value 3%', + }, + }, + { + key: 'series4', + data: 15000, + color: DefaultPalette.yellow, + legend: '2021', + xAxisCalloutData: '2021/07/30', + yAxisCalloutData: '3%', + callOutAccessibilityData: { + ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 4 of 4 2021, x value 2021/07/30, y value 3%', + }, + }, + ], + }, +]; + describe('Grouped Vertical bar chart rendering', () => { beforeEach(updateChartWidthAndHeight); afterEach(sharedAfterEach); @@ -350,6 +550,40 @@ describe('Grouped vertical bar chart - Subcomponent Legends', () => { expect(bars[5]).toHaveAttribute('opacity', ''); }, ); + + testWithoutWait( + 'Should select multiple legends on click', + GroupedVerticalBarChart, + { data: dataGVBC, legendProps: { canSelectMultipleLegends: true } }, + container => { + const firstLegend = screen.queryByText('2023')?.closest('button'); + const secondLegend = screen.queryByText('2024')?.closest('button'); + + expect(firstLegend).toBeDefined(); + expect(secondLegend).toBeDefined(); + + fireEvent.click(firstLegend!); + fireEvent.click(secondLegend!); + + // Assert + expect(firstLegend).toHaveAttribute('aria-selected', 'true'); + expect(secondLegend).toHaveAttribute('aria-selected', 'true'); + + const bars = screen.getAllByText((content, element) => element!.tagName.toLowerCase() === 'rect'); + expect(bars[0]).toHaveAttribute('opacity', '0.1'); + expect(bars[1]).toHaveAttribute('opacity', ''); + expect(bars[2]).toHaveAttribute('opacity', ''); + expect(bars[3]).toHaveAttribute('opacity', '0.1'); + expect(bars[4]).toHaveAttribute('opacity', '0.1'); + expect(bars[5]).toHaveAttribute('opacity', ''); + expect(bars[6]).toHaveAttribute('opacity', ''); + expect(bars[7]).toHaveAttribute('opacity', '0.1'); + expect(bars[8]).toHaveAttribute('opacity', '0.1'); + expect(bars[9]).toHaveAttribute('opacity', ''); + expect(bars[10]).toHaveAttribute('opacity', ''); + expect(bars[11]).toHaveAttribute('opacity', '0.1'); + }, + ); }); describe('Grouped vertical bar chart - Subcomponent callout', () => { diff --git a/packages/react-examples/src/react-charting/GroupedVerticalBarChart/GroupedVerticalBarChart.Basic.Example.tsx b/packages/react-examples/src/react-charting/GroupedVerticalBarChart/GroupedVerticalBarChart.Basic.Example.tsx index d514c24099ff2d..be8a4d5999a243 100644 --- a/packages/react-examples/src/react-charting/GroupedVerticalBarChart/GroupedVerticalBarChart.Basic.Example.tsx +++ b/packages/react-examples/src/react-charting/GroupedVerticalBarChart/GroupedVerticalBarChart.Basic.Example.tsx @@ -16,6 +16,7 @@ interface IGroupedBarChartState { hideLabels: boolean; enableGradient: boolean; roundCorners: boolean; + selectMultipleLegends: boolean; } export class GroupedVerticalBarChartBasicExample extends React.Component<{}, IGroupedBarChartState> { @@ -29,6 +30,7 @@ export class GroupedVerticalBarChartBasicExample extends React.Component<{}, IGr hideLabels: false, enableGradient: false, roundCorners: false, + selectMultipleLegends: false, }; } @@ -60,6 +62,10 @@ export class GroupedVerticalBarChartBasicExample extends React.Component<{}, IGr this.setState({ roundCorners: checked }); }; + private _onLegendMultiSelectChange = (ev: React.MouseEvent, checked: boolean) => { + this.setState({ selectMultipleLegends: checked }); + }; + private _basicExample(): JSX.Element { const data = [ { @@ -87,6 +93,28 @@ export class GroupedVerticalBarChartBasicExample extends React.Component<{}, IGr ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 2 of 2 2023, x value 2023/04/30, y value 44%', }, }, + { + key: 'series3', + data: 54000, + color: getColorFromToken(DataVizPalette.color5), + legend: '2024', + xAxisCalloutData: '2024/04/30', + yAxisCalloutData: '44%', + callOutAccessibilityData: { + ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 3 of 4 2022, x value 2024/04/30, y value 44%', + }, + }, + { + key: 'series4', + data: 24000, + color: getColorFromToken(DataVizPalette.color6), + legend: '2021', + xAxisCalloutData: '2021/04/30', + yAxisCalloutData: '44%', + callOutAccessibilityData: { + ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 4 of 4 2021, x value 2021/04/30, y value 44%', + }, + }, ], }, { @@ -114,6 +142,28 @@ export class GroupedVerticalBarChartBasicExample extends React.Component<{}, IGr ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 2 of 2 2023, x value 2023/05/30, y value 3%', }, }, + { + key: 'series3', + data: 9000, + color: getColorFromToken(DataVizPalette.color5), + legend: '2024', + xAxisCalloutData: '2024/05/30', + yAxisCalloutData: '3%', + callOutAccessibilityData: { + ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 3 of 4 2024, x value 2024/05/30, y value 3%', + }, + }, + { + key: 'series4', + data: 12000, + color: getColorFromToken(DataVizPalette.color6), + legend: '2021', + xAxisCalloutData: '2021/05/30', + yAxisCalloutData: '3%', + callOutAccessibilityData: { + ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 4 of 4 2021, x value 2021/05/30, y value 3%', + }, + }, ], }, @@ -142,6 +192,28 @@ export class GroupedVerticalBarChartBasicExample extends React.Component<{}, IGr ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 2 of 2 2023, x value 2023/06/30, y value 50%', }, }, + { + key: 'series3', + data: 60000, + color: getColorFromToken(DataVizPalette.color5), + legend: '2024', + xAxisCalloutData: '2024/06/30', + yAxisCalloutData: '50%', + callOutAccessibilityData: { + ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 3 of 4 2024, x value 2024/06/30, y value 50%', + }, + }, + { + key: 'series4', + data: 10000, + color: getColorFromToken(DataVizPalette.color6), + legend: '2021', + xAxisCalloutData: '2021/06/30', + yAxisCalloutData: '50%', + callOutAccessibilityData: { + ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 4 of 4 2021, x value 2021/06/30, y value 50%', + }, + }, ], }, { @@ -169,6 +241,28 @@ export class GroupedVerticalBarChartBasicExample extends React.Component<{}, IGr ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 2 of 2 2023, x value 2023/07/30, y value 3%', }, }, + { + key: 'series3', + data: 6000, + color: getColorFromToken(DataVizPalette.color5), + legend: '2024', + xAxisCalloutData: '2024/07/30', + yAxisCalloutData: '3%', + callOutAccessibilityData: { + ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 3 of 4 2024, x value 2024/07/30, y value 3%', + }, + }, + { + key: 'series4', + data: 15000, + color: getColorFromToken(DataVizPalette.color6), + legend: '2021', + xAxisCalloutData: '2021/07/30', + yAxisCalloutData: '3%', + callOutAccessibilityData: { + ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 4 of 4 2021, x value 2021/07/30, y value 3%', + }, + }, ], }, ]; @@ -234,6 +328,13 @@ export class GroupedVerticalBarChartBasicExample extends React.Component<{}, IGr    +    +