From 6fe1046ebcf6ccc769cb3bb0e58960fbd4e00b42 Mon Sep 17 00:00:00 2001 From: krkshitij <110246001+krkshitij@users.noreply.github.com> Date: Thu, 3 Nov 2022 23:04:52 +0530 Subject: [PATCH] Fix legends selection bugs (#24563) * fix legends selection reset bug * sync LineChart legend states * sync AreaChart legend states * sync HeatMapChart legend states * sync StackedBarChart legend states * sync VerticalBarChart legend states * sync DonutChart legend states * sync GroupedVerticalBarChart legend states * sync VerticalStackedBarChart legend states * refactor * add change file * rename function * minor * add comments --- ...-3475ad9e-70bd-41ba-8508-fb05f00a786f.json | 7 + .../components/AreaChart/AreaChart.base.tsx | 79 +++++------ .../components/DonutChart/DonutChart.base.tsx | 47 +++---- .../GroupedVerticalBarChart.base.tsx | 88 ++++++------ .../HeatMapChart/HeatMapChart.base.tsx | 108 +++++++-------- .../src/components/Legends/Legends.base.tsx | 129 ++++++------------ .../components/LineChart/LineChart.base.tsx | 34 +++-- .../MultiStackedBarChart.base.tsx | 94 ++++++------- .../StackedBarChart/StackedBarChart.base.tsx | 91 ++++++------ .../VerticalBarChart.base.tsx | 95 ++++++------- .../VerticalStackedBarChart.base.tsx | 116 ++++++++-------- 11 files changed, 409 insertions(+), 479 deletions(-) create mode 100644 change/@fluentui-react-charting-3475ad9e-70bd-41ba-8508-fb05f00a786f.json diff --git a/change/@fluentui-react-charting-3475ad9e-70bd-41ba-8508-fb05f00a786f.json b/change/@fluentui-react-charting-3475ad9e-70bd-41ba-8508-fb05f00a786f.json new file mode 100644 index 00000000000000..d36c4381bd8936 --- /dev/null +++ b/change/@fluentui-react-charting-3475ad9e-70bd-41ba-8508-fb05f00a786f.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Fix legends selection bugs", + "packageName": "@fluentui/react-charting", + "email": "kumarkshitij@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-charting/src/components/AreaChart/AreaChart.base.tsx b/packages/react-charting/src/components/AreaChart/AreaChart.base.tsx index 5b656e4121c5a0..56553ce04ddbe8 100644 --- a/packages/react-charting/src/components/AreaChart/AreaChart.base.tsx +++ b/packages/react-charting/src/components/AreaChart/AreaChart.base.tsx @@ -101,11 +101,10 @@ export class AreaChartBase extends React.Component this._createDataSet(data.lineChartData!)); this.state = { + selectedLegend: '', activeLegend: '', hoverXValue: '', isCalloutVisible: false, - isLegendSelected: false, - isLegendHovered: false, refSelected: null, YValueHover: [], lineXValue: 0, @@ -430,42 +429,28 @@ export class AreaChartBase extends React.Component { @@ -493,8 +478,8 @@ export class AreaChartBase extends React.Component { this._onLegendHover(singleChartData.legend); }, - onMouseOutAction: (isLegendSelected?: boolean) => { - this._onLegendLeave(isLegendSelected); + onMouseOutAction: () => { + this._onLegendLeave(); }, }; @@ -518,14 +503,11 @@ export class AreaChartBase extends React.Component { + private _getOpacity = (legend: string): number => { if (!this._isMultiStackChart) { return 0.7; } else { - let opacity = 0.7; - if (this.state.isLegendHovered || this.state.isLegendSelected) { - opacity = this.state.activeLegend === selectedArea ? 0.7 : 0.1; - } + const opacity = this._legendHighlighted(legend) || this._noLegendHighlighted() ? 0.7 : 0.1; return opacity; } }; @@ -538,8 +520,8 @@ export class AreaChartBase extends React.Component { + return ( + this.state.selectedLegend === legend || (this.state.selectedLegend === '' && this.state.activeLegend === legend) + ); + }; + + /** + * This function checks if none of the legends is selected or hovered. + */ + private _noLegendHighlighted = () => { + return this.state.selectedLegend === '' && this.state.activeLegend === ''; + }; } diff --git a/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx b/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx index 77834f31c56b1c..61719dae693f26 100644 --- a/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx +++ b/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx @@ -18,7 +18,6 @@ export interface IDonutChartState { _height?: number | undefined; activeLegend?: string; color?: string | undefined; - isLegendSelected?: boolean; xCalloutValue?: string; yCalloutValue?: string; focusedArcId?: string; @@ -61,10 +60,9 @@ export class DonutChartBase extends React.Component { - if (this.state.isLegendSelected) { - if (this.state.activeLegend !== point.legend || this.state.activeLegend === '') { - this.setState({ activeLegend: point.legend!, isLegendSelected: true }); - } else { - this.setState({ activeLegend: point.legend }); - } + if (this.state.selectedLegend === point.legend) { + this.setState({ selectedLegend: '' }); } else { - this.setState({ activeLegend: point.legend!, isLegendSelected: true }); + this.setState({ selectedLegend: point.legend! }); } }, hoverAction: () => { - if (this.state.activeLegend !== point.legend || this.state.activeLegend === '') { - this.setState({ activeLegend: point.legend! }); - } else { - this.setState({ activeLegend: point.legend }); - } + this.setState({ activeLegend: point.legend! }); }, onMouseOutAction: () => { - this.setState({ - showHover: false, - activeLegend: '', - }); + this.setState({ activeLegend: '' }); }, }; return legend; @@ -229,7 +216,6 @@ export class DonutChartBase extends React.Component ); @@ -240,7 +226,7 @@ export class DonutChartBase extends React.Component { - if (point.legend === this.state.activeLegend) { + if (point.legend === highlightedLegend) { legendValue = point.yAxisCalloutData ? point.yAxisCalloutData : point.data!; } return; @@ -304,4 +291,14 @@ export class DonutChartBase extends React.Component { - let shouldHighlight = true; - if (this.state.isLegendHovered || this.state.isLegendSelected) { - shouldHighlight = this.state.titleForHoverCard === legendTitle; - } - return shouldHighlight ? '' : '0.1'; + const opacity = this._legendHighlighted(legendTitle) || this._noLegendHighlighted() ? '' : '0.1'; + return opacity; }; private _onBarHover = ( @@ -234,9 +229,7 @@ export class GroupedVerticalBarChartBase extends React.Component< this.setState({ refSelected: mouseEvent, /** Show the callout if highlighted bar is hovered and Hide it if unhighlighted bar is hovered */ - isCalloutVisible: - this.state.isLegendSelected === false || - (this.state.isLegendSelected === true && this.state.titleForHoverCard === pointData.legend), + isCalloutVisible: this.state.selectedLegend === '' || this.state.selectedLegend === pointData.legend, calloutLegend: pointData.legend, dataForHoverCard: pointData.data, color: pointData.color, @@ -272,9 +265,7 @@ export class GroupedVerticalBarChartBase extends React.Component< this.setState({ refSelected: obj.refElement, /** Show the callout if highlighted bar is focused and Hide it if unhighlighted bar is focused */ - isCalloutVisible: - this.state.isLegendSelected === false || - (this.state.isLegendSelected === true && this.state.titleForHoverCard === pointData.legend), + isCalloutVisible: this.state.selectedLegend === '' || this.state.selectedLegend === pointData.legend, calloutLegend: pointData.legend, dataForHoverCard: pointData.data, color: pointData.color, @@ -441,43 +432,28 @@ export class GroupedVerticalBarChartBase extends React.Component< }); }; - private _onLegendClick(customMessage: string): void { - if (this.state.isLegendSelected) { - if (this.state.titleForHoverCard === customMessage) { - this.setState({ - isLegendSelected: false, - titleForHoverCard: customMessage, - }); - } else { - this.setState({ - titleForHoverCard: customMessage, - }); - } + private _onLegendClick(legendTitle: string): void { + if (this.state.selectedLegend === legendTitle) { + this.setState({ + selectedLegend: '', + }); } else { this.setState({ - isLegendSelected: true, - titleForHoverCard: customMessage, + selectedLegend: legendTitle, }); } } - private _onLegendHover(customMessage: string): void { - if (this.state.isLegendSelected === false) { - this.setState({ - isLegendHovered: true, - titleForHoverCard: customMessage, - }); - } + private _onLegendHover(legendTitle: string): void { + this.setState({ + activeLegend: legendTitle, + }); } - private _onLegendLeave(isLegendFocused?: boolean): void { - if (!!isLegendFocused || this.state.isLegendSelected === false) { - this.setState({ - isLegendHovered: false, - titleForHoverCard: '', - isLegendSelected: isLegendFocused ? false : this.state.isLegendSelected, - }); - } + private _onLegendLeave(): void { + this.setState({ + activeLegend: '', + }); } private _getLegendData = (points: IGroupedVerticalBarChartData[], palette: IPalette): JSX.Element => { @@ -501,8 +477,8 @@ export class GroupedVerticalBarChartBase extends React.Component< hoverAction: () => { this._onLegendHover(point.legend); }, - onMouseOutAction: (isLegendSelected?: boolean) => { - this._onLegendLeave(isLegendSelected); + onMouseOutAction: () => { + this._onLegendLeave(); }, }; @@ -526,4 +502,24 @@ export class GroupedVerticalBarChartBase extends React.Component< this._yMax = Math.max(domainValue[domainValue.length - 1], this.props.yMaxValue || 0); } }; + + /** + * This function checks if the given legend is highlighted or not. + * A legend can be highlighted in 2 ways: + * 1. selection: if the user clicks on it + * 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) + ); + }; + + /** + * This function checks if none of the legends is selected or hovered. + */ + private _noLegendHighlighted = () => { + return this.state.selectedLegend === '' && this.state.activeLegend === ''; + }; } diff --git a/packages/react-charting/src/components/HeatMapChart/HeatMapChart.base.tsx b/packages/react-charting/src/components/HeatMapChart/HeatMapChart.base.tsx index 1de69743d1f6d1..12ef54eb312788 100644 --- a/packages/react-charting/src/components/HeatMapChart/HeatMapChart.base.tsx +++ b/packages/react-charting/src/components/HeatMapChart/HeatMapChart.base.tsx @@ -41,19 +41,13 @@ interface IRectRef { type RectanglesGraphData = { [key: string]: FlattenData[] }; export interface IHeatMapChartState { /** - * determines if the legend any of the legend is selected or not - * @default false + * contains the selected legend string */ - isLegendSelected: boolean; + selectedLegend: string; /** - * contains the seleted legend string + * contains the hovered legend string */ activeLegend: string; - /** - * determines if the legend is hovered or not - * @default false - */ - isLegendHovered: boolean; /** * determines wethere to show or hide the callout * @default false @@ -142,9 +136,8 @@ export class HeatMapChartBase extends React.Component this._createNewDataSet(data, xDate, xNum, yDate, yNum), ); this.state = { - isLegendSelected: false, + selectedLegend: '', activeLegend: '', - isLegendHovered: false, isCalloutVisible: false, target: null, calloutLegend: '', @@ -243,11 +236,8 @@ export class HeatMapChartBase extends React.Component { - let shouldHighlight = true; - if (this.state.isLegendHovered || this.state.isLegendSelected) { - shouldHighlight = legendTitle === this.state.activeLegend; - } - return shouldHighlight ? '1' : '0.1'; + const opacity = this._legendHighlighted(legendTitle) || this._noLegendHighlighted() ? '1' : '0.1'; + return opacity; }; private _rectRefCallback = (rectElement: SVGGElement, index: number | string, dataPointObject: FlattenData): void => { @@ -258,9 +248,7 @@ export class HeatMapChartBase extends React.Component { - if (this.state.isLegendSelected === false) { - this.setState({ - activeLegend: legendTitle, - isLegendHovered: true, - }); - } + this.setState({ + activeLegend: legendTitle, + }); }; /** * when the mouse is out from the legend , we need - * to show the graph in initial mode. isLegendFocused will - * be useful at the scenario where mouseout happend for - * the legends which are in overflow card - * @param isLegendFocused + * to show the graph in initial mode. */ - private _onLegendLeave = (isLegendFocused?: boolean): void => { - if (!!isLegendFocused || this.state.isLegendSelected === false) { - this.setState({ - activeLegend: '', - isLegendHovered: false, - isLegendSelected: isLegendFocused ? false : this.state.isLegendSelected, - }); - } + private _onLegendLeave = (): void => { + this.setState({ + activeLegend: '', + }); }; /** * @param legendTitle @@ -401,27 +376,16 @@ export class HeatMapChartBase extends React.Component { /** * check if the legend is already selceted, - * if yes, then check if the consumer has clicked already - * seleceted legend if yes un-select the legend, else - * set the acitve legend state to legendTitle - * - * if legend is not alredy selceted, simply set the isLegendSelected to true - * and the active legend to the legendTitle of selected legend + * if yes, un-select the legend, else + * set the selected legend state to legendTitle */ - if (this.state.isLegendSelected) { - if (this.state.activeLegend === legendTitle) { - this.setState({ - activeLegend: '', - isLegendSelected: false, - }); - } else { - this.setState({ activeLegend: legendTitle }); - } + if (this.state.selectedLegend === legendTitle) { + this.setState({ + selectedLegend: '', + }); } else { this.setState({ - activeLegend: legendTitle, - isLegendSelected: true, - isLegendHovered: false, + selectedLegend: legendTitle, }); } }; @@ -438,8 +402,8 @@ export class HeatMapChartBase extends React.Component { this._onLegendHover(item.legend); }, - onMouseOutAction: (isLegendSelected?: boolean) => { - this._onLegendLeave(isLegendSelected); + onMouseOutAction: () => { + this._onLegendLeave(); }, }; legends.push(legend); @@ -693,4 +657,24 @@ export class HeatMapChartBase extends React.Component { + return ( + this.state.selectedLegend === legendTitle || + (this.state.selectedLegend === '' && this.state.activeLegend === legendTitle) + ); + }; + + /** + * This function checks if none of the legends is selected or hovered. + */ + private _noLegendHighlighted = () => { + return this.state.selectedLegend === '' && this.state.activeLegend === ''; + }; } diff --git a/packages/react-charting/src/components/Legends/Legends.base.tsx b/packages/react-charting/src/components/Legends/Legends.base.tsx index 9c1615e22de136..49a02253b28dde 100644 --- a/packages/react-charting/src/components/Legends/Legends.base.tsx +++ b/packages/react-charting/src/components/Legends/Legends.base.tsx @@ -38,9 +38,7 @@ interface ILegendItem extends React.ButtonHTMLAttributes { export interface ILegendState { selectedLegend: string; - selecetedLegendInHoverCard: string; - selectedState: boolean; - hoverState: boolean; + activeLegend: string; isHoverCardVisible: boolean; selectedLegends: string[]; } @@ -51,21 +49,13 @@ export class LegendsBase extends React.Component { public constructor(props: ILegendsProps) { super(props); this.state = { - selectedLegend: 'none', - selectedState: false, - hoverState: false, + selectedLegend: '', + activeLegend: '', isHoverCardVisible: false, - selecetedLegendInHoverCard: 'none', selectedLegends: [], }; } - public componentDidUpdate(prevProps: ILegendsProps) { - if (prevProps.selectedLegend !== this.props.selectedLegend) { - this.setState({ selectedLegend: this.props.selectedLegend! }); - } - } - public render(): JSX.Element { const { theme, className, styles } = this.props; this._classNames = getClassNames(styles!, { @@ -169,12 +159,6 @@ export class LegendsBase extends React.Component { this.setState({ //check if user selected all legends then empty it get the default behaviour selectedLegends: selectedLegends.length === this.props.legends.length ? [] : selectedLegends, - // make selectedState to false if none or all the legends selected - selectedState: - selectedLegends.length === 0 || selectedLegends.length === this.props.legends.length ? false : true, - selecetedLegendInHoverCard: this.state.isHoverCardVisible ? legend.title : 'none', - selectedLegend: 'none', - hoverState: false, }); }; @@ -185,18 +169,13 @@ export class LegendsBase extends React.Component { */ private _canSelectOnlySingleLegend = (legend: ILegend): void => { - if (this.state.selectedState === true && this.state.selectedLegend === legend.title) { + if (this.state.selectedLegend === legend.title) { this.setState({ - selectedLegend: 'none', - selectedState: false, - selecetedLegendInHoverCard: this.state.isHoverCardVisible ? legend.title : 'none', + selectedLegend: '', }); } else { this.setState({ - hoverState: true, - selectedState: true, selectedLegend: legend.title, - selecetedLegendInHoverCard: this.state.isHoverCardVisible ? legend.title : 'none', }); } }; @@ -257,36 +236,15 @@ export class LegendsBase extends React.Component { const overflowString = overflowText ? overflowText : 'more'; // execute similar to "_onClick" and "_onLeave" logic at HoverCard onCardHide event const onHoverCardHideHandler = () => { - const { canSelectMultipleLegends = false } = this.props; - const selectedOverflowItem = find( - legends, - (legend: ILegend) => - legend.title === this.state.selecetedLegendInHoverCard || legend.title === this.state.selectedLegend, - ); - this.setState( - { - isHoverCardVisible: false, - selecetedLegendInHoverCard: 'none', - selectedLegends: [], - }, - () => { - if (selectedOverflowItem) { - this.setState({ selectedLegend: 'none', selectedState: false }, () => { - if (selectedOverflowItem.action && !canSelectMultipleLegends) { - selectedOverflowItem.action(); - } - if (this.props.onLegendHoverCardLeave && canSelectMultipleLegends) { - this.props.onLegendHoverCardLeave(); - } - this.setState({ hoverState: false }, () => { - if (selectedOverflowItem.onMouseOutAction) { - selectedOverflowItem.onMouseOutAction(true); - } - }); - }); - } - }, - ); + this.setState({ isHoverCardVisible: false }); + /** Unhighlight the focused legend in the hover card */ + const activeOverflowItem = find(legends, (legend: ILegend) => legend.title === this.state.activeLegend); + if (activeOverflowItem) { + this.setState({ activeLegend: '' }); + if (activeOverflowItem.onMouseOutAction) { + activeOverflowItem.onMouseOutAction(); + } + } }; return ( { }; private _onHoverOverLegend = (legend: ILegend) => { - if (!this.state.selectedState) { - if (legend.hoverAction) { - this.setState({ hoverState: true, selectedLegend: legend.title }); - legend.hoverAction(); - } + if (legend.hoverAction) { + this.setState({ activeLegend: legend.title }); + legend.hoverAction(); } }; private _onLeave = (legend: ILegend) => { - if (!this.state.selectedState) { - if (legend.onMouseOutAction) { - this.setState({ hoverState: false, selectedLegend: 'none' }); - legend.onMouseOutAction(); - } + if (legend.onMouseOutAction) { + this.setState({ activeLegend: '' }); + legend.onMouseOutAction(); } }; @@ -425,30 +379,31 @@ export class LegendsBase extends React.Component { } private _getColor(title: string, color: string): string { - const { theme, canSelectMultipleLegends = false } = this.props; + const { theme } = this.props; const { palette } = theme!; let legendColor = color; - // below if statement will get executed for the hovered legend. - // (which is also the slected legend see fucntion:-_onHoverOverLegend) - if (this.state.hoverState && this.state.selectedLegend === title) { - legendColor = color; - } // below esle if statement will get executed for the unselected-legend which is hovered - else if (this.state.hoverState && this.state.selectedLegend !== 'none' && this.state.selectedLegend !== title) { - legendColor = palette.white; - } // below else if statement will get executed if the legends are in the selected state - // this is will only get executed when we have ability to select multiple legends - else if (!!canSelectMultipleLegends && this.state.selectedState && this.state.selectedLegends.indexOf(title) > -1) { - legendColor = color; - } // below else if statement will get executed when no legend is selected and hovered - //(for example:- initial render of legends) - else if ( - (!this.state.selectedState && this.state.selectedLegend === 'none') || - this.state.selectedLegend === title || - this.state.selectedLegends.length <= 0 - ) { - legendColor = color; + const inSelectedState = this.state.selectedLegend !== '' || this.state.selectedLegends.length > 0; + if (inSelectedState) { + /** if one or more legends are selected */ + if (this.state.selectedLegend === title || this.state.selectedLegends.indexOf(title) > -1) { + /** if the given legend (title) is one of the selected legends */ + legendColor = color; + } else { + /** if the given legend is unselected */ + legendColor = palette.white; + } } else { - legendColor = palette.white; + /** if no legend is selected */ + if (this.state.activeLegend === title || this.state.activeLegend === '') { + /** + * if the given legend is hovered + * or none of the legends is hovered + */ + legendColor = color; + } else { + /** if there is a hovered legend but the given legend is not the one */ + legendColor = palette.white; + } } return legendColor; } diff --git a/packages/react-charting/src/components/LineChart/LineChart.base.tsx b/packages/react-charting/src/components/LineChart/LineChart.base.tsx index bb85fd85ca9b39..89057a530674e0 100644 --- a/packages/react-charting/src/components/LineChart/LineChart.base.tsx +++ b/packages/react-charting/src/components/LineChart/LineChart.base.tsx @@ -349,13 +349,10 @@ export class LineChartBase extends React.Component { if (this.state.selectedLegend === lineChartItem.legend) { - this.setState({ selectedLegend: '', activeLegend: lineChartItem.legend }); + this.setState({ selectedLegend: '' }); this._handleLegendClick(lineChartItem, null); } else { - this.setState({ - selectedLegend: lineChartItem.legend, - activeLegend: lineChartItem.legend, - }); + this.setState({ selectedLegend: lineChartItem.legend }); this._handleLegendClick(lineChartItem, lineChartItem.legend); } }; @@ -578,7 +575,7 @@ export class LineChartBase extends React.Component { + return ( + this.state.selectedLegend === legend || (this.state.selectedLegend === '' && this.state.activeLegend === legend) + ); + }; + + /** + * This function checks if none of the legends is selected or hovered. + */ + private _noLegendHighlighted = () => { + return this.state.selectedLegend === '' && this.state.activeLegend === ''; + }; } diff --git a/packages/react-charting/src/components/StackedBarChart/MultiStackedBarChart.base.tsx b/packages/react-charting/src/components/StackedBarChart/MultiStackedBarChart.base.tsx index 4ac1f4bf92f2a9..b70b03f16f2c61 100644 --- a/packages/react-charting/src/components/StackedBarChart/MultiStackedBarChart.base.tsx +++ b/packages/react-charting/src/components/StackedBarChart/MultiStackedBarChart.base.tsx @@ -25,13 +25,12 @@ export interface IRefArrayData { export interface IMultiStackedBarChartState { isCalloutVisible: boolean; refArray: IRefArrayData[]; - selectedLegendTitle: string; + selectedLegend: string; + activeLegend: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any refSelected: any; dataForHoverCard: number; color: string; - isLegendHovered: boolean; - isLegendSelected: boolean; xCalloutValue?: string; yCalloutValue?: string; dataPointCalloutProps?: IChartDataPoint; @@ -55,12 +54,11 @@ export class MultiStackedBarChartBase extends React.Component { @@ -344,8 +334,8 @@ export class MultiStackedBarChartBase extends React.Component { this._onHover(point.legend!); }, - onMouseOutAction: (isLegendSelected?: boolean) => { - this._onLeave(isLegendSelected); + onMouseOutAction: () => { + this._onLeave(); }, }; actions.push(legend); @@ -369,8 +359,8 @@ export class MultiStackedBarChartBase extends React.Component { this._onHover(point.legend!); }, - onMouseOutAction: (isLegendSelected?: boolean) => { - this._onLeave(isLegendSelected); + onMouseOutAction: () => { + this._onLeave(); }, }; actions.push(legend); @@ -388,34 +378,22 @@ export class MultiStackedBarChartBase extends React.Component { + return ( + this.state.selectedLegend === legendTitle || + (this.state.selectedLegend === '' && this.state.activeLegend === legendTitle) + ); + }; + + /** + * This function checks if none of the legends is selected or hovered. + */ + private _noLegendHighlighted = () => { + return this.state.selectedLegend === '' && this.state.activeLegend === ''; + }; } diff --git a/packages/react-charting/src/components/StackedBarChart/StackedBarChart.base.tsx b/packages/react-charting/src/components/StackedBarChart/StackedBarChart.base.tsx index 51927d30b4caea..8ef5446fb7f8ca 100644 --- a/packages/react-charting/src/components/StackedBarChart/StackedBarChart.base.tsx +++ b/packages/react-charting/src/components/StackedBarChart/StackedBarChart.base.tsx @@ -12,13 +12,12 @@ import { TooltipHost, TooltipOverflowMode } from '@fluentui/react'; const getClassNames = classNamesFunction(); export interface IStackedBarChartState { isCalloutVisible: boolean; - selectedLegendTitle: string; + selectedLegend: string; + activeLegend: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any refSelected: any; dataForHoverCard: number; color: string; - isLegendHovered: boolean; - isLegendSelected: boolean; xCalloutValue?: string; yCalloutValue?: string; dataPointCalloutProps?: IChartDataPoint; @@ -42,12 +41,11 @@ export class StackedBarChartBase extends React.Component 0 - ? (isLegendFocused?: boolean) => { - this._onLeave(isLegendFocused); + ? () => { + this._onLeave(); } : undefined, }; @@ -265,10 +263,7 @@ export class StackedBarChartBase extends React.Component { + return ( + this.state.selectedLegend === legendTitle || + (this.state.selectedLegend === '' && this.state.activeLegend === legendTitle) + ); + }; + + /** + * This function checks if none of the legends is selected or hovered. + */ + private _noLegendHighlighted = () => { + return this.state.selectedLegend === '' && this.state.activeLegend === ''; + }; } diff --git a/packages/react-charting/src/components/VerticalBarChart/VerticalBarChart.base.tsx b/packages/react-charting/src/components/VerticalBarChart/VerticalBarChart.base.tsx index 8082709ab61357..526ae8e1882c83 100644 --- a/packages/react-charting/src/components/VerticalBarChart/VerticalBarChart.base.tsx +++ b/packages/react-charting/src/components/VerticalBarChart/VerticalBarChart.base.tsx @@ -40,7 +40,6 @@ enum CircleVisbility { } const getClassNames = classNamesFunction(); export interface IVerticalBarChartState extends IBasestate { - selectedLegendTitle: string; dataPointCalloutProps?: IVerticalBarChartDataPoint; // define this in hover and focus /** * data point of x, where rectangle is hovered or focused @@ -77,10 +76,9 @@ export class VerticalBarChartBase extends React.Component (!isNumericAxis ? xBarScale(d.x) + 0.5 * xBarScale.bandwidth() : xScale(d.x))) // eslint-disable-next-line @typescript-eslint/no-explicit-any .y((d: any) => yScale(d.y)); - let shouldHighlight = true; - if (this.state.isLegendHovered || this.state.isLegendSelected) { - shouldHighlight = this.state.selectedLegendTitle === lineLegendText; - } + const shouldHighlight = this._legendHighlighted(lineLegendText!) || this._noLegendHighlighted() ? true : false; const lineBorderWidth = this.props.lineOptions?.lineBorderWidth ? Number.parseFloat(this.props.lineOptions!.lineBorderWidth!.toString()) : 0; @@ -396,9 +391,7 @@ export class VerticalBarChartBase extends React.Component { - let shouldHighlight = true; - if (this.state.isLegendHovered || this.state.isLegendSelected) { - shouldHighlight = this.state.selectedLegendTitle === point.legend; - } + const shouldHighlight = this._legendHighlighted(point.legend!) || this._noLegendHighlighted() ? true : false; this._classNames = getClassNames(this.props.styles!, { theme: this.props.theme!, legendColor: this.state.color, @@ -616,43 +604,28 @@ export class VerticalBarChartBase extends React.Component { @@ -671,8 +644,8 @@ export class VerticalBarChartBase extends React.Component { this._onLegendHover(point.legend!); }, - onMouseOutAction: (isLegendSelected?: boolean) => { - this._onLegendLeave(isLegendSelected); + onMouseOutAction: () => { + this._onLegendLeave(); }, }; actions.push(legend); @@ -687,8 +660,8 @@ export class VerticalBarChartBase extends React.Component { this._onLegendHover(lineLegendText); }, - onMouseOutAction: (isLegendSelected?: boolean) => { - this._onLegendLeave(isLegendSelected); + onMouseOutAction: () => { + this._onLegendLeave(); }, isLineLegendInBarChart: true, }; @@ -713,4 +686,24 @@ export class VerticalBarChartBase extends React.Component { + return ( + this.state.selectedLegend === legendTitle || + (this.state.selectedLegend === '' && this.state.activeLegend === legendTitle) + ); + }; + + /** + * This function checks if none of the legends is selected or hovered. + */ + private _noLegendHighlighted = () => { + return this.state.selectedLegend === '' && this.state.activeLegend === ''; + }; } diff --git a/packages/react-charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx b/packages/react-charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx index 077784687cf7e7..e48a732770b19b 100644 --- a/packages/react-charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx +++ b/packages/react-charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx @@ -60,7 +60,6 @@ enum CircleVisbility { } export interface IVerticalStackedBarChartState extends IBasestate { - selectedLegendTitle: string; dataPointCalloutProps?: IVSChartDataPoint; stackCalloutProps?: IVerticalStackedChartProps; activeXAxisDataPoint: number | string; @@ -92,9 +91,8 @@ export class VerticalStackedBarChartBase extends React.Component< super(props); this.state = { isCalloutVisible: false, - isLegendSelected: false, - isLegendHovered: false, - selectedLegendTitle: '', + selectedLegend: '', + activeLegend: '', refSelected: null, dataForHoverCard: 0, color: '', @@ -214,7 +212,7 @@ export class VerticalStackedBarChartBase extends React.Component< const { isCalloutForStack = false } = this.props; let shouldFocusStackOnly: boolean = false; if (_isHavingLines) { - if (this.state.isLegendSelected) { + if (this.state.selectedLegend !== '') { shouldFocusStackOnly = false; } else { shouldFocusStackOnly = true; @@ -270,7 +268,6 @@ export class VerticalStackedBarChartBase extends React.Component< containerHeight: number, containerWidth: number, ): JSX.Element => { - const { isLegendHovered, isLegendSelected, selectedLegendTitle } = this.state; const isNumeric = this._xAxisType === XAxisTypes.NumericAxis; const { xBarScale } = this._getScales(containerHeight, containerWidth, isNumeric); const lineObject: LineObject = this._getFormattedLineData(this.props.data); @@ -284,10 +281,7 @@ export class VerticalStackedBarChartBase extends React.Component< // eslint-disable-next-line @typescript-eslint/no-explicit-any const xScaleBandwidthTranslate = isNumeric ? 0 : (xBarScale as any).bandwidth() / 2; Object.keys(lineObject).forEach((item: string, index: number) => { - let shouldHighlight = true; - if (isLegendHovered || isLegendSelected) { - shouldHighlight = selectedLegendTitle === item; // item is legend name; - } + const shouldHighlight = this._legendHighlighted(item) || this._noLegendHighlighted(); // item is legend name for (let i = 1; i < lineObject[item].length; i++) { const x1 = isNumeric ? xScale(lineObject[item][i - 1].xItem.xAxisPoint as number) @@ -329,11 +323,10 @@ export class VerticalStackedBarChartBase extends React.Component< strokeLinecap="round" stroke={lineObject[item][i].color} transform={`translate(${xScaleBandwidthTranslate}, 0)`} - {...(isLegendSelected && - selectedLegendTitle === item && { - onMouseOver: this._lineHover.bind(this, lineObject[item][i - 1]), - onMouseLeave: this._lineHoverOut, - })} + {...(this.state.selectedLegend === item && { + onMouseOver: this._lineHover.bind(this, lineObject[item][i - 1]), + onMouseLeave: this._lineHoverOut, + })} />, ); } @@ -351,14 +344,13 @@ export class VerticalStackedBarChartBase extends React.Component< } cy={yScale(circlePoint.y)} onMouseOver={ - isLegendSelected && selectedLegendTitle === item + this.state.selectedLegend === item ? this._lineHover.bind(this, circlePoint) : this._onStackHover.bind(this, circlePoint.xItem) } - {...(isLegendSelected && - selectedLegendTitle === item && { - onMouseLeave: this._lineHoverOut, - })} + {...(this.state.selectedLegend === item && { + onMouseLeave: this._lineHoverOut, + })} r={this._getCircleVisibilityAndRadius(circlePoint.xItem.xAxisPoint, circlePoint.legend).radius} stroke={circlePoint.color} fill={this.props.theme!.palette.white} @@ -382,11 +374,11 @@ export class VerticalStackedBarChartBase extends React.Component< xAxispoint: string | number, legend: string, ): { visibility: CircleVisbility; radius: number } => { - const { isLegendSelected, activeXAxisDataPoint, selectedLegendTitle } = this.state; - if (isLegendSelected) { - if (xAxispoint === activeXAxisDataPoint && legend === selectedLegendTitle) { + const { selectedLegend, activeXAxisDataPoint } = this.state; + if (selectedLegend !== '') { + if (xAxispoint === activeXAxisDataPoint && selectedLegend === legend) { return { visibility: CircleVisbility.show, radius: 8 }; - } else if (legend === selectedLegendTitle) { + } else if (selectedLegend === legend) { return { visibility: CircleVisbility.show, radius: 0.3 }; } else { return { visibility: CircleVisbility.hide, radius: 0 }; @@ -455,43 +447,28 @@ export class VerticalStackedBarChartBase extends React.Component< : null; }; - private _onLegendClick(customMessage: string): void { - if (this.state.isLegendSelected) { - if (this.state.selectedLegendTitle === customMessage) { - this.setState({ - isLegendSelected: false, - selectedLegendTitle: customMessage, - }); - } else { - this.setState({ - selectedLegendTitle: customMessage, - }); - } + private _onLegendClick(legendTitle: string): void { + if (this.state.selectedLegend === legendTitle) { + this.setState({ + selectedLegend: '', + }); } else { this.setState({ - isLegendSelected: true, - selectedLegendTitle: customMessage, + selectedLegend: legendTitle, }); } } - private _onLegendHover(customMessage: string): void { - if (this.state.isLegendSelected === false) { - this.setState({ - isLegendHovered: true, - selectedLegendTitle: customMessage, - }); - } + private _onLegendHover(legendTitle: string): void { + this.setState({ + activeLegend: legendTitle, + }); } - private _onLegendLeave(isLegendFocused?: boolean): void { - if (!!isLegendFocused || this.state.isLegendSelected === false) { - this.setState({ - isLegendHovered: false, - selectedLegendTitle: '', - isLegendSelected: isLegendFocused ? false : this.state.isLegendSelected, - }); - } + private _onLegendLeave(): void { + this.setState({ + activeLegend: '', + }); } private _getLegendData( @@ -521,7 +498,7 @@ export class VerticalStackedBarChartBase extends React.Component< this._onLegendClick(point.legend); }, hoverAction: allowHoverOnLegend ? () => this._onLegendHover(point.legend) : undefined, - onMouseOutAction: allowHoverOnLegend ? isLegendSelected => this._onLegendLeave(isLegendSelected) : undefined, + onMouseOutAction: allowHoverOnLegend ? () => this._onLegendLeave() : undefined, }; actions.push(legend); @@ -538,7 +515,7 @@ export class VerticalStackedBarChartBase extends React.Component< this._onLegendClick(point.title); }, hoverAction: allowHoverOnLegend ? () => this._onLegendHover(point.title) : undefined, - onMouseOutAction: allowHoverOnLegend ? isLegendSelected => this._onLegendLeave(isLegendSelected) : undefined, + onMouseOutAction: allowHoverOnLegend ? () => this._onLegendLeave() : undefined, }; legendsOfLine.push(legend); }); @@ -580,9 +557,7 @@ export class VerticalStackedBarChartBase extends React.Component< * Show the callout if highlighted bar is focused/hovered * and Hide it if unhighlighted bar is focused/hovered */ - isCalloutVisible: - this.state.isLegendSelected === false || - (this.state.isLegendSelected === true && this.state.selectedLegendTitle === point.legend), + isCalloutVisible: this.state.selectedLegend === '' || this.state.selectedLegend === point.legend, calloutLegend: point.legend, dataForHoverCard: point.data, color, @@ -741,10 +716,7 @@ export class VerticalStackedBarChartBase extends React.Component< const color = point.color ? point.color : this._colors[index]; const ref: IRefArrayData = {}; - let shouldHighlight = true; - if (this.state.isLegendHovered || this.state.isLegendSelected) { - shouldHighlight = this.state.selectedLegendTitle === point.legend; - } + const shouldHighlight = this._legendHighlighted(point.legend) || this._noLegendHighlighted() ? true : false; const classNames = getClassNames(this.props.styles!, { theme: this.props.theme!, shouldHighlight: shouldHighlight, @@ -934,4 +906,24 @@ export class VerticalStackedBarChartBase extends React.Component< this._yMax = Math.max(domainValue[domainValue.length - 1], this.props.yMaxValue || 0); } }; + + /** + * This function checks if the given legend is highlighted or not. + * A legend can be highlighted in 2 ways: + * 1. selection: if the user clicks on it + * 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) + ); + }; + + /** + * This function checks if none of the legends is selected or hovered. + */ + private _noLegendHighlighted = () => { + return this.state.selectedLegend === '' && this.state.activeLegend === ''; + }; }