Skip to content

Commit 37e5f0b

Browse files
authored
[GVBC] Enabling multiple legend selection for Grouped Vertical Bar Chart (#33511)
1 parent 6a70a11 commit 37e5f0b

File tree

4 files changed

+375
-23
lines changed

4 files changed

+375
-23
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Enabling multiple legend selection for Grouped Vertical Bar Chart",
4+
"packageName": "@fluentui/react-charting",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/charts/react-charting/src/components/GroupedVerticalBarChart/GroupedVerticalBarChart.base.tsx

+30-23
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export interface IGroupedVerticalBarChartState extends IBasestate {
7373
dataPointCalloutProps?: IGVBarChartSeriesPoint;
7474
callOutAccessibilityData?: IAccessibilityProps;
7575
calloutLegend: string;
76+
selectedLegends: string[];
7677
}
7778

7879
export class GroupedVerticalBarChartBase
@@ -121,7 +122,7 @@ export class GroupedVerticalBarChartBase
121122
dataForHoverCard: 0,
122123
isCalloutVisible: false,
123124
refSelected: null,
124-
selectedLegend: props.legendProps?.selectedLegend ?? '',
125+
selectedLegends: props.legendProps?.selectedLegends || [],
125126
xCalloutValue: '',
126127
yCalloutValue: '',
127128
YValueHover: [],
@@ -333,7 +334,7 @@ export class GroupedVerticalBarChartBase
333334
this.setState({
334335
refSelected: mouseEvent,
335336
/** Show the callout if highlighted bar is hovered and Hide it if unhighlighted bar is hovered */
336-
isCalloutVisible: this.state.selectedLegend === '' || this.state.selectedLegend === pointData.legend,
337+
isCalloutVisible: this._noLegendHighlighted() || this._legendHighlighted(pointData.legend),
337338
calloutLegend: pointData.legend,
338339
dataForHoverCard: pointData.data,
339340
color: pointData.color,
@@ -369,7 +370,7 @@ export class GroupedVerticalBarChartBase
369370
this.setState({
370371
refSelected: obj.refElement,
371372
/** Show the callout if highlighted bar is focused and Hide it if unhighlighted bar is focused */
372-
isCalloutVisible: this.state.selectedLegend === '' || this.state.selectedLegend === pointData.legend,
373+
isCalloutVisible: this._noLegendHighlighted() || this._legendHighlighted(pointData.legend),
373374
calloutLegend: pointData.legend,
374375
dataForHoverCard: pointData.data,
375376
color: pointData.color,
@@ -581,18 +582,6 @@ export class GroupedVerticalBarChartBase
581582
});
582583
};
583584

584-
private _onLegendClick(legendTitle: string): void {
585-
if (this.state.selectedLegend === legendTitle) {
586-
this.setState({
587-
selectedLegend: '',
588-
});
589-
} else {
590-
this.setState({
591-
selectedLegend: legendTitle,
592-
});
593-
}
594-
}
595-
596585
private _onLegendHover(legendTitle: string): void {
597586
this.setState({
598587
activeLegend: legendTitle,
@@ -624,9 +613,6 @@ export class GroupedVerticalBarChartBase
624613
const legend: ILegend = {
625614
title: point.legend,
626615
color,
627-
action: () => {
628-
this._onLegendClick(point.legend);
629-
},
630616
hoverAction: () => {
631617
this._handleChartMouseLeave();
632618
this._onLegendHover(point.legend);
@@ -646,10 +632,26 @@ export class GroupedVerticalBarChartBase
646632
enabledWrapLines={this.props.enabledLegendsWrapLines}
647633
focusZonePropsInHoverCard={this.props.focusZonePropsForLegendsInHoverCard}
648634
{...this.props.legendProps}
635+
onChange={this._onLegendSelectionChange.bind(this)}
649636
/>
650637
);
651638
};
652639

640+
private _onLegendSelectionChange(
641+
selectedLegends: string[],
642+
event: React.MouseEvent<HTMLButtonElement>,
643+
currentLegend?: ILegend,
644+
): void {
645+
if (this.props.legendProps?.canSelectMultipleLegends) {
646+
this.setState({ selectedLegends });
647+
} else {
648+
this.setState({ selectedLegends: selectedLegends.slice(-1) });
649+
}
650+
if (this.props.legendProps?.onChange) {
651+
this.props.legendProps.onChange(selectedLegends, event, currentLegend);
652+
}
653+
}
654+
653655
private _getAxisData = (yAxisData: IAxisData) => {
654656
if (yAxisData && yAxisData.yAxisDomainValues.length) {
655657
const { yAxisDomainValues: domainValue } = yAxisData;
@@ -664,19 +666,24 @@ export class GroupedVerticalBarChartBase
664666
* 2. hovering: if there is no selected legend and the user hovers over it
665667
*/
666668
private _legendHighlighted = (legendTitle: string) => {
667-
return (
668-
this.state.selectedLegend === legendTitle ||
669-
(this.state.selectedLegend === '' && this.state.activeLegend === legendTitle)
670-
);
669+
return this._getHighlightedLegend().includes(legendTitle!);
671670
};
672671

673672
/**
674673
* This function checks if none of the legends is selected or hovered.
675674
*/
676675
private _noLegendHighlighted = () => {
677-
return this.state.selectedLegend === '' && this.state.activeLegend === '';
676+
return this._getHighlightedLegend().length === 0;
678677
};
679678

679+
private _getHighlightedLegend() {
680+
return this.state.selectedLegends.length > 0
681+
? this.state.selectedLegends
682+
: this.state.activeLegend
683+
? [this.state.activeLegend]
684+
: [];
685+
}
686+
680687
private _getAriaLabel = (point: IGVBarChartSeriesPoint, xAxisPoint: string): string => {
681688
const xValue = point.xAxisCalloutData || xAxisPoint;
682689
const legend = point.legend;

packages/charts/react-charting/src/components/GroupedVerticalBarChart/GroupedVerticalBarChartRTL.test.tsx

+234
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,206 @@ const chartPoints = [
139139
},
140140
];
141141

142+
const dataGVBC = [
143+
{
144+
name: 'Jan - Mar',
145+
series: [
146+
{
147+
key: 'series1',
148+
data: 33000,
149+
color: DefaultPalette.blue,
150+
legend: '2022',
151+
xAxisCalloutData: '2022/04/30',
152+
yAxisCalloutData: '29%',
153+
callOutAccessibilityData: {
154+
ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 1 of 2 2022, x value 2022/04/30, y value 29%',
155+
},
156+
},
157+
{
158+
key: 'series2',
159+
data: 44000,
160+
color: DefaultPalette.green,
161+
legend: '2023',
162+
xAxisCalloutData: '2023/04/30',
163+
yAxisCalloutData: '44%',
164+
callOutAccessibilityData: {
165+
ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 2 of 2 2023, x value 2023/04/30, y value 44%',
166+
},
167+
},
168+
{
169+
key: 'series3',
170+
data: 54000,
171+
color: DefaultPalette.red,
172+
legend: '2024',
173+
xAxisCalloutData: '2024/04/30',
174+
yAxisCalloutData: '44%',
175+
callOutAccessibilityData: {
176+
ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 3 of 4 2022, x value 2024/04/30, y value 44%',
177+
},
178+
},
179+
{
180+
key: 'series4',
181+
data: 24000,
182+
color: DefaultPalette.yellow,
183+
legend: '2021',
184+
xAxisCalloutData: '2021/04/30',
185+
yAxisCalloutData: '44%',
186+
callOutAccessibilityData: {
187+
ariaLabel: 'Group Jan - Mar 1 of 4, Bar series 4 of 4 2021, x value 2021/04/30, y value 44%',
188+
},
189+
},
190+
],
191+
},
192+
{
193+
name: 'Apr - Jun',
194+
series: [
195+
{
196+
key: 'series1',
197+
data: 33000,
198+
color: DefaultPalette.blue,
199+
legend: '2022',
200+
xAxisCalloutData: '2022/05/30',
201+
yAxisCalloutData: '29%',
202+
callOutAccessibilityData: {
203+
ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 1 of 2 2022, x value 2022/05/30, y value 29%',
204+
},
205+
},
206+
{
207+
key: 'series2',
208+
data: 3000,
209+
color: DefaultPalette.green,
210+
legend: '2023',
211+
xAxisCalloutData: '2023/05/30',
212+
yAxisCalloutData: '3%',
213+
callOutAccessibilityData: {
214+
ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 2 of 2 2023, x value 2023/05/30, y value 3%',
215+
},
216+
},
217+
{
218+
key: 'series3',
219+
data: 9000,
220+
color: DefaultPalette.red,
221+
legend: '2024',
222+
xAxisCalloutData: '2024/05/30',
223+
yAxisCalloutData: '3%',
224+
callOutAccessibilityData: {
225+
ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 3 of 4 2024, x value 2024/05/30, y value 3%',
226+
},
227+
},
228+
{
229+
key: 'series4',
230+
data: 12000,
231+
color: DefaultPalette.yellow,
232+
legend: '2021',
233+
xAxisCalloutData: '2021/05/30',
234+
yAxisCalloutData: '3%',
235+
callOutAccessibilityData: {
236+
ariaLabel: 'Group Apr - Jun 2 of 4, Bar series 4 of 4 2021, x value 2021/05/30, y value 3%',
237+
},
238+
},
239+
],
240+
},
241+
242+
{
243+
name: 'Jul - Sep',
244+
series: [
245+
{
246+
key: 'series1',
247+
data: 14000,
248+
color: DefaultPalette.blue,
249+
legend: '2022',
250+
xAxisCalloutData: '2022/06/30',
251+
yAxisCalloutData: '13%',
252+
callOutAccessibilityData: {
253+
ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 1 of 2 2022, x value 2022/06/30, y value 13%',
254+
},
255+
},
256+
{
257+
key: 'series2',
258+
data: 50000,
259+
color: DefaultPalette.green,
260+
legend: '2023',
261+
xAxisCalloutData: '2023/06/30',
262+
yAxisCalloutData: '50%',
263+
callOutAccessibilityData: {
264+
ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 2 of 2 2023, x value 2023/06/30, y value 50%',
265+
},
266+
},
267+
{
268+
key: 'series3',
269+
data: 60000,
270+
color: DefaultPalette.red,
271+
legend: '2024',
272+
xAxisCalloutData: '2024/06/30',
273+
yAxisCalloutData: '50%',
274+
callOutAccessibilityData: {
275+
ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 3 of 4 2024, x value 2024/06/30, y value 50%',
276+
},
277+
},
278+
{
279+
key: 'series4',
280+
data: 10000,
281+
color: DefaultPalette.yellow,
282+
legend: '2021',
283+
xAxisCalloutData: '2021/06/30',
284+
yAxisCalloutData: '50%',
285+
callOutAccessibilityData: {
286+
ariaLabel: 'Group Jul - Sep 3 of 4, Bar series 4 of 4 2021, x value 2021/06/30, y value 50%',
287+
},
288+
},
289+
],
290+
},
291+
{
292+
name: 'Oct - Dec',
293+
series: [
294+
{
295+
key: 'series1',
296+
data: 33000,
297+
color: DefaultPalette.blue,
298+
legend: '2022',
299+
xAxisCalloutData: '2022/07/30',
300+
yAxisCalloutData: '29%',
301+
callOutAccessibilityData: {
302+
ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 1 of 2 2022, x value 2022/07/30, y value 29%',
303+
},
304+
},
305+
{
306+
key: 'series2',
307+
data: 3000,
308+
color: DefaultPalette.green,
309+
legend: '2023',
310+
xAxisCalloutData: '2023/07/30',
311+
yAxisCalloutData: '3%',
312+
callOutAccessibilityData: {
313+
ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 2 of 2 2023, x value 2023/07/30, y value 3%',
314+
},
315+
},
316+
{
317+
key: 'series3',
318+
data: 6000,
319+
color: DefaultPalette.red,
320+
legend: '2024',
321+
xAxisCalloutData: '2024/07/30',
322+
yAxisCalloutData: '3%',
323+
callOutAccessibilityData: {
324+
ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 3 of 4 2024, x value 2024/07/30, y value 3%',
325+
},
326+
},
327+
{
328+
key: 'series4',
329+
data: 15000,
330+
color: DefaultPalette.yellow,
331+
legend: '2021',
332+
xAxisCalloutData: '2021/07/30',
333+
yAxisCalloutData: '3%',
334+
callOutAccessibilityData: {
335+
ariaLabel: 'Group Oct - Dec 4 of 4, Bar series 4 of 4 2021, x value 2021/07/30, y value 3%',
336+
},
337+
},
338+
],
339+
},
340+
];
341+
142342
describe('Grouped Vertical bar chart rendering', () => {
143343
beforeEach(updateChartWidthAndHeight);
144344
afterEach(sharedAfterEach);
@@ -350,6 +550,40 @@ describe('Grouped vertical bar chart - Subcomponent Legends', () => {
350550
expect(bars[5]).toHaveAttribute('opacity', '');
351551
},
352552
);
553+
554+
testWithoutWait(
555+
'Should select multiple legends on click',
556+
GroupedVerticalBarChart,
557+
{ data: dataGVBC, legendProps: { canSelectMultipleLegends: true } },
558+
container => {
559+
const firstLegend = screen.queryByText('2023')?.closest('button');
560+
const secondLegend = screen.queryByText('2024')?.closest('button');
561+
562+
expect(firstLegend).toBeDefined();
563+
expect(secondLegend).toBeDefined();
564+
565+
fireEvent.click(firstLegend!);
566+
fireEvent.click(secondLegend!);
567+
568+
// Assert
569+
expect(firstLegend).toHaveAttribute('aria-selected', 'true');
570+
expect(secondLegend).toHaveAttribute('aria-selected', 'true');
571+
572+
const bars = screen.getAllByText((content, element) => element!.tagName.toLowerCase() === 'rect');
573+
expect(bars[0]).toHaveAttribute('opacity', '0.1');
574+
expect(bars[1]).toHaveAttribute('opacity', '');
575+
expect(bars[2]).toHaveAttribute('opacity', '');
576+
expect(bars[3]).toHaveAttribute('opacity', '0.1');
577+
expect(bars[4]).toHaveAttribute('opacity', '0.1');
578+
expect(bars[5]).toHaveAttribute('opacity', '');
579+
expect(bars[6]).toHaveAttribute('opacity', '');
580+
expect(bars[7]).toHaveAttribute('opacity', '0.1');
581+
expect(bars[8]).toHaveAttribute('opacity', '0.1');
582+
expect(bars[9]).toHaveAttribute('opacity', '');
583+
expect(bars[10]).toHaveAttribute('opacity', '');
584+
expect(bars[11]).toHaveAttribute('opacity', '0.1');
585+
},
586+
);
353587
});
354588

355589
describe('Grouped vertical bar chart - Subcomponent callout', () => {

0 commit comments

Comments
 (0)