From 2bcc3103d228c58e1eb3aadbf0b6e82b275fe17c Mon Sep 17 00:00:00 2001 From: "Atishay Jain (atisjai)" <98592573+AtishayMsft@users.noreply.github.com> Date: Thu, 12 Dec 2024 04:13:24 +0000 Subject: [PATCH 1/7] Control legend selection in declarative chart --- .../DeclarativeChart/DeclarativeChart.tsx | 58 +++++++++++++------ .../DeclarativeChart/PlotlySchemaAdapter.ts | 4 -- .../DeclarativeChart.Basic.Example.tsx | 2 +- .../DeclarativeChart/schema/fluent_area.json | 3 +- .../DeclarativeChart/schema/fluent_line.json | 3 +- 5 files changed, 44 insertions(+), 26 deletions(-) diff --git a/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx b/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx index 7c27bfc94b9112..fc035d16250b38 100644 --- a/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx +++ b/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx @@ -24,29 +24,22 @@ import { SankeyChart } from '../SankeyChart/SankeyChart'; import { GaugeChart } from '../GaugeChart/index'; import { GroupedVerticalBarChart } from '../GroupedVerticalBarChart/index'; import { VerticalBarChart } from '../VerticalBarChart/index'; - -import { useTheme } from '@fluentui/react'; +import { useTheme } from '@fluentui/react/lib/Theme'; export const UseIsDarkTheme = (): boolean => { const theme = useTheme(); return theme.isInverted; }; +/** + * DeclarativeChart schema. + * {@docCategory DeclarativeChart} + */ export interface Schema { /** * Plotly schema represented as JSON object */ plotlySchema: any; - - /** - * The legends selected by the user to persist in the chart - */ - selectedLegends?: string[]; - - /** - * Dictionary for localizing the accessibility labels - */ - accesibilityLabels?: { [key: string]: string }; } /** @@ -79,17 +72,26 @@ export const DeclarativeChart: React.FunctionComponent = DeclarativeChartProps >((props, forwardedRef) => { const { plotlySchema } = props.chartSchema; - const xValues = plotlySchema.data[0].x; + const { data, layout, selectedLegends } = plotlySchema; + const xValues = data[0].x; const isXDate = isDateArray(xValues); const isXNumber = isNumberArray(xValues); const colorMap = useColorMapping(); const isDarkTheme = UseIsDarkTheme(); - switch (plotlySchema.data[0].type) { + const [activeLegends, setActiveLegends] = React.useState(selectedLegends ?? []); + const onActiveLegendsChange = (keys: string[]) => { + setActiveLegends(keys); + if (props.onSchemaChange) { + props.onSchemaChange({ plotlySchema: { data, layout, selectedLegends: keys } }); + } + }; + + switch (data[0].type) { case 'pie': return ; case 'bar': - const orientation = plotlySchema.data[0].orientation; + const orientation = data[0].orientation; if (orientation === 'h') { return ( = return ; } case 'scatter': - const isAreaChart = plotlySchema.data.some((series: any) => series.fill === 'tonexty'); + const isAreaChart = data.some((series: any) => series.fill === 'tonexty'); if (isXDate || isXNumber) { if (isAreaChart) { - return ; + return ( + 0 ? activeLegends[0] : '', + onChange: onActiveLegendsChange, + }} + /> + ); } - return ; + return ( + + ); } return ; case 'heatmap': @@ -116,7 +136,7 @@ export const DeclarativeChart: React.FunctionComponent = case 'sankey': return ; case 'indicator': - if (plotlySchema?.data?.[0]?.mode?.includes('gauge')) { + if (data?.[0]?.mode?.includes('gauge')) { return ; } return
Unsupported Schema
; diff --git a/packages/charts/react-charting/src/components/DeclarativeChart/PlotlySchemaAdapter.ts b/packages/charts/react-charting/src/components/DeclarativeChart/PlotlySchemaAdapter.ts index 5fd4e09b3330da..2d60ea87769a32 100644 --- a/packages/charts/react-charting/src/components/DeclarativeChart/PlotlySchemaAdapter.ts +++ b/packages/charts/react-charting/src/components/DeclarativeChart/PlotlySchemaAdapter.ts @@ -1,7 +1,6 @@ /* eslint-disable one-var */ /* eslint-disable vars-on-top */ /* eslint-disable no-var */ -/* eslint-disable no-console */ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from 'react'; import { bin as d3Bin, extent as d3Extent, sum as d3Sum, min as d3Min, max as d3Max, merge as d3Merge } from 'd3-array'; @@ -533,9 +532,6 @@ var baseContainer: any, baseAttrName: any; export function findArrayAttributes(trace: any) { // Init basecontainer and baseAttrName crawlIntoTrace(baseContainer, 0, ''); - for (const attribute of arrayAttributes) { - console.log(attribute); - } } function crawlIntoTrace(container: any, i: number, astrPartial: any) { diff --git a/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx b/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx index e9b67f09913939..3e4404b25917bb 100644 --- a/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx +++ b/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx @@ -38,7 +38,7 @@ export class DeclarativeChartBasicExample extends React.Component<{}, IDeclarati constructor(props: DeclarativeChartProps) { super(props); this.state = { - selectedChoice: 'donutchart', + selectedChoice: 'areachart', }; } diff --git a/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_area.json b/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_area.json index 90940675770645..9bb4f973a4df9a 100644 --- a/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_area.json +++ b/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_area.json @@ -99,5 +99,6 @@ "plot_bgcolor": "#F5F6F9", "paper_bgcolor": "#F5F6F9" }, - "frames": [] + "frames": [], + "selectedLegends": ["a"] } diff --git a/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_line.json b/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_line.json index 4197999e2576fe..2ddb971520d9a1 100644 --- a/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_line.json +++ b/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_line.json @@ -435,5 +435,6 @@ "plot_bgcolor": "#F5F6F9", "paper_bgcolor": "#F5F6F9" }, - "frames": [] + "frames": [], + "selectedLegends": ["Trace 0"] } From 30c9eeefdaafd12f805b104c6003561427c2da38 Mon Sep 17 00:00:00 2001 From: "Atishay Jain (atisjai)" <98592573+AtishayMsft@users.noreply.github.com> Date: Sat, 14 Dec 2024 03:41:59 +0000 Subject: [PATCH 2/7] Add legendprops for single selection and example to test the behavior --- .../DeclarativeChart/DeclarativeChart.tsx | 71 ++++++++++++++----- .../DeclarativeChart.Basic.Example.tsx | 56 ++++++++++++--- .../DeclarativeChart/schema/fluent_donut.json | 3 +- 3 files changed, 101 insertions(+), 29 deletions(-) diff --git a/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx b/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx index 1dcb6b87498cd7..12b4a9c009ff76 100644 --- a/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx +++ b/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx @@ -87,22 +87,52 @@ export const DeclarativeChart: React.FunctionComponent = } }; + let legendProps; + if (activeLegends.length > 0) { + legendProps = { + canSelectMultipleLegends: false, + selectedLegend: activeLegends[0], + onChange: onActiveLegendsChange, + }; + } else { + legendProps = { + canSelectMultipleLegends: false, + onChange: onActiveLegendsChange, + }; + } + switch (data[0].type) { case 'pie': - return ; + return ( + + ); case 'bar': const orientation = data[0].orientation; if (orientation === 'h') { return ( ); } else { if (['group', 'overlay'].includes(plotlySchema?.layout?.barmode)) { - return ; + return ( + + ); } - return ; + return ( + + ); } case 'scatter': const isAreaChart = data.some((series: any) => series.fill === 'tonexty'); @@ -111,37 +141,44 @@ export const DeclarativeChart: React.FunctionComponent = return ( 0 ? activeLegends[0] : '', - onChange: onActiveLegendsChange, - }} + legendProps={legendProps} /> ); } return ( ); } - return ; + return ( + + ); case 'heatmap': - return ; + return ; case 'sankey': return ; case 'indicator': if (data?.[0]?.mode?.includes('gauge')) { - return ; + return ( + + ); } return
Unsupported Schema
; case 'histogram': - return ; + return ( + + ); default: return
Unsupported Schema
; } diff --git a/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx b/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx index 3e4404b25917bb..a2bb9edf4c0ef8 100644 --- a/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx +++ b/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx @@ -1,9 +1,14 @@ import * as React from 'react'; import { Dropdown, IDropdownOption } from '@fluentui/react/lib/Dropdown'; +import { Toggle } from '@fluentui/react/lib/Toggle'; import { DeclarativeChart, DeclarativeChartProps, Schema } from '@fluentui/react-charting'; +// Issue - on clicking on selected legend again, legend selection is not getting cleared. + interface IDeclarativeChartState { selectedChoice: string; + preSelectLegends: boolean; + selectedLegends: string; } const options: IDropdownOption[] = [ @@ -38,7 +43,9 @@ export class DeclarativeChartBasicExample extends React.Component<{}, IDeclarati constructor(props: DeclarativeChartProps) { super(props); this.state = { - selectedChoice: 'areachart', + selectedChoice: 'donutchart', + preSelectLegends: false, + selectedLegends: '', }; } @@ -47,7 +54,16 @@ export class DeclarativeChartBasicExample extends React.Component<{}, IDeclarati } private _onChange = (ev: React.FormEvent, option: IDropdownOption): void => { - this.setState({ selectedChoice: option.key as string }); + this.setState({ selectedChoice: option.key as string, selectedLegends: '' }); + }; + + private _onTogglePreselectLegends = (ev: React.MouseEvent, checked: boolean) => { + this.setState({ preSelectLegends: checked }); + }; + + private _handleChartSchemaChanged = (eventData: Schema) => { + const { selectedLegends } = eventData.plotlySchema; + this.setState({ selectedLegends: selectedLegends.join(', ') }); }; private _getSchemaByKey(key: string): any { @@ -57,20 +73,38 @@ export class DeclarativeChartBasicExample extends React.Component<{}, IDeclarati private _createDeclarativeChart(): JSX.Element { const selectedPlotlySchema = this._getSchemaByKey(this.state.selectedChoice); - const inputSchema: Schema = { plotlySchema: selectedPlotlySchema }; + const uniqueKey = `${this.state.selectedChoice}_${this.state.preSelectLegends}`; + let inputSchema: Schema = { plotlySchema: selectedPlotlySchema }; + + if (this.state.preSelectLegends === false) { + const { data, layout } = selectedPlotlySchema; + inputSchema = { plotlySchema: { data, layout } }; + } return ( <> - +
+ +     + +
+

+
- + Legend selection changed : {this.state.selectedLegends} ); } diff --git a/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_donut.json b/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_donut.json index 27aa22a439d13d..41426b2dbbd8ea 100644 --- a/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_donut.json +++ b/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_donut.json @@ -60,5 +60,6 @@ "hovermode": "closest", "showlegend": true }, - "frames": [] + "frames": [], + "selectedLegends": ["Cadillac"] } From 384d69c73cf70ca174e0c5857b5aaba07e830f8e Mon Sep 17 00:00:00 2001 From: "Atishay Jain (atisjai)" <98592573+AtishayMsft@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:59:47 +0000 Subject: [PATCH 3/7] Do not override user performed selection of the legends --- .../src/components/Legends/Legends.base.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/charts/react-charting/src/components/Legends/Legends.base.tsx b/packages/charts/react-charting/src/components/Legends/Legends.base.tsx index 44e2cbaeec1ccd..4e3b7ce0116a9b 100644 --- a/packages/charts/react-charting/src/components/Legends/Legends.base.tsx +++ b/packages/charts/react-charting/src/components/Legends/Legends.base.tsx @@ -195,16 +195,6 @@ export class LegendsBase extends React.Component { return { primary, overflow }; }; - /** - * Determine whether the component is in "controlled" mode for selections, where the selected legend(s) are - * determined entirely by props passed in from the parent component. - */ - private _isInControlledMode = (): boolean => { - return this.props.canSelectMultipleLegends - ? this.props.selectedLegends !== undefined - : this.props.selectedLegend !== undefined; - }; - /** * Get the new selected legends based on the legend that was clicked when multi-select is enabled. * @param legend The legend that was clicked @@ -242,9 +232,7 @@ export class LegendsBase extends React.Component { ? this._getNewSelectedLegendsForMultiselect(legend) : this._getNewSelectedLegendsForSingleSelect(legend); - if (!this._isInControlledMode()) { - this.setState({ selectedLegends: nextSelectedLegends }); - } + this.setState({ selectedLegends: nextSelectedLegends }); this.props.onChange?.(Object.keys(nextSelectedLegends), event, legend); legend.action?.(); }; From 79773308185228c9a985bcf96d1c12ae212c9d99 Mon Sep 17 00:00:00 2001 From: "Atishay Jain (atisjai)" <98592573+AtishayMsft@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:00:27 +0000 Subject: [PATCH 4/7] Remove issue comment --- .../DeclarativeChart/DeclarativeChart.Basic.Example.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx b/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx index a2bb9edf4c0ef8..e1e95218a39a98 100644 --- a/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx +++ b/packages/react-examples/src/react-charting/DeclarativeChart/DeclarativeChart.Basic.Example.tsx @@ -3,8 +3,6 @@ import { Dropdown, IDropdownOption } from '@fluentui/react/lib/Dropdown'; import { Toggle } from '@fluentui/react/lib/Toggle'; import { DeclarativeChart, DeclarativeChartProps, Schema } from '@fluentui/react-charting'; -// Issue - on clicking on selected legend again, legend selection is not getting cleared. - interface IDeclarativeChartState { selectedChoice: string; preSelectLegends: boolean; From fc4376565338069f3fd616a4e7e720c709177b73 Mon Sep 17 00:00:00 2001 From: "Atishay Jain (atisjai)" <98592573+AtishayMsft@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:40:01 +0000 Subject: [PATCH 5/7] Add change file --- ...eact-charting-779871c9-4df7-4cc5-96b6-832621cec379.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-charting-779871c9-4df7-4cc5-96b6-832621cec379.json diff --git a/change/@fluentui-react-charting-779871c9-4df7-4cc5-96b6-832621cec379.json b/change/@fluentui-react-charting-779871c9-4df7-4cc5-96b6-832621cec379.json new file mode 100644 index 00000000000000..3838228b1446ec --- /dev/null +++ b/change/@fluentui-react-charting-779871c9-4df7-4cc5-96b6-832621cec379.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Add support for controlling legend selection and persisting in json schema", + "packageName": "@fluentui/react-charting", + "email": "98592573+AtishayMsft@users.noreply.github.com", + "dependentChangeType": "patch" +} From ff942855e48d2338e6f0479b74eb10babbe5134a Mon Sep 17 00:00:00 2001 From: "Atishay Jain (atisjai)" <98592573+AtishayMsft@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:56:29 +0000 Subject: [PATCH 6/7] Update api description --- packages/charts/react-charting/etc/react-charting.api.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/charts/react-charting/etc/react-charting.api.md b/packages/charts/react-charting/etc/react-charting.api.md index 543e8d624e2679..54135c0d0cea04 100644 --- a/packages/charts/react-charting/etc/react-charting.api.md +++ b/packages/charts/react-charting/etc/react-charting.api.md @@ -1603,13 +1603,9 @@ export const PieChart: React_2.FunctionComponent; // @public export const SankeyChart: React_2.FunctionComponent; -// @public (undocumented) +// @public export interface Schema { - accesibilityLabels?: { - [key: string]: string; - }; plotlySchema: any; - selectedLegends?: string[]; } // @public (undocumented) From ef9fbb4516a376e991199d62f6003d1ff28193ba Mon Sep 17 00:00:00 2001 From: "Atishay Jain (atisjai)" <98592573+AtishayMsft@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:12:07 +0000 Subject: [PATCH 7/7] remove if else in favour of spread operator --- .../DeclarativeChart/DeclarativeChart.tsx | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx b/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx index 12b4a9c009ff76..55164e9de2c577 100644 --- a/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx +++ b/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx @@ -87,19 +87,11 @@ export const DeclarativeChart: React.FunctionComponent = } }; - let legendProps; - if (activeLegends.length > 0) { - legendProps = { - canSelectMultipleLegends: false, - selectedLegend: activeLegends[0], - onChange: onActiveLegendsChange, - }; - } else { - legendProps = { - canSelectMultipleLegends: false, - onChange: onActiveLegendsChange, - }; - } + const legendProps = { + canSelectMultipleLegends: false, + onChange: onActiveLegendsChange, + ...(activeLegends.length > 0 && { selectedLegend: activeLegends[0] }), + }; switch (data[0].type) { case 'pie':