Skip to content

Commit

Permalink
Merge branch 'master' into users/srmukher/AreaMultiSel
Browse files Browse the repository at this point in the history
  • Loading branch information
srmukher authored Dec 27, 2024
2 parents bcd4d8f + fc4f676 commit ca60b85
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import * as React from 'react';
import type { Meta } from '@storybook/react';
import { SwatchPicker } from '@fluentui/react-swatch-picker';
import { SampleSwatchPickerColors, SampleSwatchPickerImages, SampleSwatchPickerGrid, steps } from './utils';
import { SampleSwatchPickerColors, SampleSwatchPickerImages, SampleSwatchPickerGrid } from './utils';
import { Steps } from 'storywright';

import { DARK_MODE, getStoryVariant, HIGH_CONTRAST, RTL, withStoryWrightSteps } from '../../utilities';

export default {
title: 'SwatchPicker Converged',
decorators: [story => withStoryWrightSteps({ story, steps })],
decorators: [
story => withStoryWrightSteps({ story, steps: new Steps().snapshot('default', { cropTo: '.testWrapper' }).end() }),
],
} satisfies Meta<typeof SwatchPicker>;

export const Default = () => (
Expand All @@ -20,7 +23,6 @@ export const Default = () => (
<SampleSwatchPickerGrid />
</>
);
Default.storyName = 'default';

export const DefaultDarkMode = getStoryVariant(Default, DARK_MODE);

Expand Down Expand Up @@ -64,7 +66,7 @@ export const Shape = () => (
<SampleSwatchPickerGrid shape="circular" />
</>
);
Size.storyName = 'shape';
Shape.storyName = 'shape';

export const Spacing = () => (
<>
Expand All @@ -79,4 +81,4 @@ export const Spacing = () => (
<SampleSwatchPickerGrid spacing="small" />
</>
);
Size.storyName = 'spacing';
Spacing.storyName = 'spacing';
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
import * as React from 'react';
import { Steps } from 'storywright';
import {
SwatchPicker,
ColorSwatch,
SwatchPickerProps,
type SwatchPickerProps,
ImageSwatch,
EmptySwatch,
SwatchPickerRow,
} from '@fluentui/react-swatch-picker';
import { HeartRegular } from '@fluentui/react-icons';

export const steps = new Steps()
.snapshot('default', { cropTo: '.testWrapper' })
.hover('.breadcrumb-sample')
.snapshot('hover', { cropTo: '.testWrapper' })
.mouseDown('.breadcrumb-sample')
.snapshot('pressed', { cropTo: '.testWrapper' })
.focus('.breadcrumb-sample')
.snapshot('focused', { cropTo: '.testWrapper' })
.end();

export const SampleSwatchPickerColors = (props: SwatchPickerProps) => (
<SwatchPicker defaultSelectedValue="00B053" {...props}>
<ColorSwatch color="#FF1921" value="FF1921" aria-label="red" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Select multiple legends for Vertical bar chart",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,18 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
[exportAsImage],
);

const multiSelectLegendProps = {
...legendProps,
canSelectMultipleLegends: true,
selectedLegends: activeLegends,
};

switch (data[0].type) {
case 'pie':
return (
<DonutChart
{...transformPlotlyJsonToDonutProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={{ ...legendProps, canSelectMultipleLegends: true, selectedLegends: activeLegends }}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
// Bubble event to prevent right click to open menu on the callout
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
Expand All @@ -169,7 +175,7 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
return (
<GroupedVerticalBarChart
{...transformPlotlyJsonToGVBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={{ ...legendProps, canSelectMultipleLegends: true, selectedLegends: activeLegends }}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
Expand All @@ -178,7 +184,7 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
return (
<VerticalStackedBarChart
{...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={{ ...legendProps, canSelectMultipleLegends: true, selectedLegends: activeLegends }}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
Expand Down Expand Up @@ -232,7 +238,7 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
return (
<VerticalStackedBarChart
{...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={{ ...legendProps, canSelectMultipleLegends: true, selectedLegends: activeLegends }}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
Expand Down Expand Up @@ -270,7 +276,7 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
return (
<VerticalBarChart
{...transformPlotlyJsonToVBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
createStringYAxis,
formatDate,
getNextGradient,
areArraysEqual,
} from '../../utilities/index';
import { IChart } from '../../types/index';

Expand All @@ -73,6 +74,7 @@ export interface IVerticalBarChartState extends IBasestate {
hoverXValue?: string | number | null;
callOutAccessibilityData?: IAccessibilityProps;
calloutLegend: string;
selectedLegends: string[];
}

type ColorScale = (_p?: number) => string;
Expand Down Expand Up @@ -118,8 +120,8 @@ export class VerticalBarChartBase
dataForHoverCard: 0,
isCalloutVisible: false,
refSelected: null,
selectedLegend: props.legendProps?.selectedLegend ?? '',
activeLegend: '',
selectedLegends: props.legendProps?.selectedLegends || [],
activeLegend: undefined,
xCalloutValue: '',
yCalloutValue: '',
activeXdataPoint: null,
Expand All @@ -141,9 +143,9 @@ export class VerticalBarChartBase
}

public componentDidUpdate(prevProps: IVerticalBarChartProps): void {
if (prevProps.legendProps?.selectedLegend !== this.props.legendProps?.selectedLegend) {
if (!areArraysEqual(prevProps.legendProps?.selectedLegends, this.props.legendProps?.selectedLegends)) {
this.setState({
selectedLegend: this.props.legendProps?.selectedLegend ?? '',
selectedLegends: this.props.legendProps?.selectedLegends || [],
});
}
}
Expand Down Expand Up @@ -199,7 +201,8 @@ export class VerticalBarChartBase
createYAxis={createNumericYAxis}
calloutProps={calloutProps}
tickParams={tickParams}
{...(this._isHavingLine && this._noLegendHighlighted() && { isCalloutForStack: true })}
{...(this._isHavingLine &&
(this._noLegendHighlighted() || this._getHighlightedLegend().length > 1) && { isCalloutForStack: true })}
legendBars={legendBars}
datasetForXAxisDomain={this._xAxisLabels}
barwidth={this._barWidth}
Expand Down Expand Up @@ -404,11 +407,11 @@ export class VerticalBarChartBase
xAxisPoint: string | number | Date,
legend: string,
): { visibility: CircleVisbility; radius: number } => {
const { selectedLegend, activeXdataPoint } = this.state;
if (selectedLegend !== '') {
if (xAxisPoint === activeXdataPoint && selectedLegend === legend) {
const { activeXdataPoint } = this.state;
if (!this._noLegendHighlighted()) {
if (xAxisPoint === activeXdataPoint && this._legendHighlighted(legend)) {
return { visibility: CircleVisbility.show, radius: 8 };
} else if (selectedLegend === legend) {
} else if (this._legendHighlighted(legend)) {
// Don't hide the circle to keep it focusable. For more information,
// see https://fuzzbomb.github.io/accessibility-demos/visually-hidden-focus-test.html
return { visibility: CircleVisbility.show, radius: 0.3 };
Expand Down Expand Up @@ -539,7 +542,11 @@ export class VerticalBarChartBase
: this._createColors()(1);

// there might be no y value of the line for the hovered bar. so we need to check this condition
if (this._isHavingLine && selectedPoint[0].lineData?.y !== undefined) {
if (
this._isHavingLine &&
selectedPoint[0].lineData?.y !== undefined &&
(this._legendHighlighted(lineLegendText) || this._noLegendHighlighted())
) {
// callout data for the line
YValueHover.push({
legend: lineLegendText,
Expand All @@ -549,18 +556,20 @@ export class VerticalBarChartBase
yAxisCalloutData: selectedPoint[0].lineData?.yAxisCalloutData,
});
}
// callout data for the bar
YValueHover.push({
legend: selectedPoint[0].legend,
y: selectedPoint[0].y,
color: enableGradient
? useSingleColor
? getNextGradient(0, 0)[0]
: selectedPoint[0].gradient?.[0] || getNextGradient(pointIndex, 0)[0]
: calloutColor,
data: selectedPoint[0].yAxisCalloutData,
yAxisCalloutData: selectedPoint[0].yAxisCalloutData,
});
if (this._legendHighlighted(selectedPoint[0].legend) || this._noLegendHighlighted()) {
// callout data for the bar
YValueHover.push({
legend: selectedPoint[0].legend,
y: selectedPoint[0].y,
color: enableGradient
? useSingleColor
? getNextGradient(0, 0)[0]
: selectedPoint[0].gradient?.[0] || getNextGradient(pointIndex, 0)[0]
: calloutColor,
data: selectedPoint[0].yAxisCalloutData,
yAxisCalloutData: selectedPoint[0].yAxisCalloutData,
});
}
const hoverXValue = point.x instanceof Date ? formatDate(point.x, this.props.useUTC) : point.x.toString();
return {
YValueHover,
Expand All @@ -581,7 +590,7 @@ export class VerticalBarChartBase
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 === point.legend,
isCalloutVisible: this._noLegendHighlighted() || this._legendHighlighted(point.legend),
dataForHoverCard: point.y,
calloutLegend: point.legend!,
color: point.color || color,
Expand All @@ -592,7 +601,7 @@ export class VerticalBarChartBase
yCalloutValue: point.yAxisCalloutData!,
dataPointCalloutProps: point,
// Hovering over a bar should highlight corresponding line points only when no legend is selected
activeXdataPoint: this._noLegendHighlighted() ? point.x : null,
activeXdataPoint: this._noLegendHighlighted() || this._legendHighlighted(point.legend) ? point.x : null,
YValueHover,
hoverXValue,
callOutAccessibilityData: point.callOutAccessibilityData,
Expand Down Expand Up @@ -621,7 +630,7 @@ export class VerticalBarChartBase
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 === point.legend,
isCalloutVisible: this._noLegendHighlighted() || this._legendHighlighted(point.legend),
calloutLegend: point.legend!,
dataForHoverCard: point.y,
color: point.color || color,
Expand Down Expand Up @@ -1059,18 +1068,6 @@ export class VerticalBarChartBase
});
};

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,
Expand All @@ -1079,7 +1076,7 @@ export class VerticalBarChartBase

private _onLegendLeave(): void {
this.setState({
activeLegend: '',
activeLegend: undefined,
});
}

Expand All @@ -1106,9 +1103,6 @@ export class VerticalBarChartBase
const legend: ILegend = {
title: legendTitle,
color,
action: () => {
this._onLegendClick(legendTitle);
},
hoverAction: () => {
this._handleChartMouseLeave();
this._onLegendHover(legendTitle);
Expand All @@ -1123,9 +1117,6 @@ export class VerticalBarChartBase
const lineLegend: ILegend = {
title: lineLegendText,
color: lineLegendColor,
action: () => {
this._onLegendClick(lineLegendText);
},
hoverAction: () => {
this._handleChartMouseLeave();
this._onLegendHover(lineLegendText);
Expand All @@ -1145,11 +1136,27 @@ export class VerticalBarChartBase
focusZonePropsInHoverCard={this.props.focusZonePropsForLegendsInHoverCard}
overflowText={this.props.legendsOverflowText}
{...this.props.legendProps}
onChange={this._onLegendSelectionChange.bind(this)}
/>
);
return legends;
};

private _onLegendSelectionChange(
selectedLegends: string[],
event: React.MouseEvent<HTMLButtonElement>,
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;
Expand All @@ -1164,20 +1171,25 @@ export class VerticalBarChartBase
* 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)
);
private _legendHighlighted = (legendTitle: string | undefined) => {
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: IVerticalBarChartDataPoint): string => {
const xValue = point.xAxisCalloutData
? point.xAxisCalloutData
Expand Down
Loading

0 comments on commit ca60b85

Please sign in to comment.