Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for controlling legend selection and persisting in json schema #33477

Merged
merged 9 commits into from
Dec 17, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
AtishayMsft marked this conversation as resolved.
Show resolved Hide resolved
"type": "patch",
"comment": "Add support for controlling legend selection and persisting in json schema",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
6 changes: 1 addition & 5 deletions packages/charts/react-charting/etc/react-charting.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1603,13 +1603,9 @@ export const PieChart: React_2.FunctionComponent<IPieChartProps>;
// @public
export const SankeyChart: React_2.FunctionComponent<ISankeyChartProps>;

// @public (undocumented)
// @public
export interface Schema {
accesibilityLabels?: {
[key: string]: string;
};
plotlySchema: any;
selectedLegends?: string[];
}

// @public (undocumented)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? false;
};

/**
* 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 };
}

/**
Expand Down Expand Up @@ -79,49 +72,105 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
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<string[]>(selectedLegends ?? []);
const onActiveLegendsChange = (keys: string[]) => {
setActiveLegends(keys);
if (props.onSchemaChange) {
props.onSchemaChange({ plotlySchema: { data, layout, selectedLegends: keys } });
}
};

const legendProps = {
canSelectMultipleLegends: false,
onChange: onActiveLegendsChange,
...(activeLegends.length > 0 && { selectedLegend: activeLegends[0] }),
};

AtishayMsft marked this conversation as resolved.
Show resolved Hide resolved
switch (data[0].type) {
case 'pie':
return <DonutChart {...transformPlotlyJsonToDonutProps(plotlySchema, colorMap, isDarkTheme)} />;
return (
<DonutChart
{...transformPlotlyJsonToDonutProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
/>
);
case 'bar':
const orientation = plotlySchema.data[0].orientation;
const orientation = data[0].orientation;
if (orientation === 'h') {
return (
<HorizontalBarChartWithAxis
{...transformPlotlyJsonToHorizontalBarWithAxisProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
/>
);
} else {
if (['group', 'overlay'].includes(plotlySchema?.layout?.barmode)) {
return <GroupedVerticalBarChart {...transformPlotlyJsonToGVBCProps(plotlySchema, colorMap, isDarkTheme)} />;
return (
<GroupedVerticalBarChart
{...transformPlotlyJsonToGVBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
/>
);
}
return <VerticalStackedBarChart {...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme)} />;
return (
<VerticalStackedBarChart
{...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
/>
);
}
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 <AreaChart {...transformPlotlyJsonToScatterChartProps(plotlySchema, true, colorMap, isDarkTheme)} />;
return (
<AreaChart
{...transformPlotlyJsonToScatterChartProps({ data, layout }, true, colorMap, isDarkTheme)}
legendProps={legendProps}
/>
);
}
return <LineChart {...transformPlotlyJsonToScatterChartProps(plotlySchema, false, colorMap, isDarkTheme)} />;
return (
<LineChart
{...transformPlotlyJsonToScatterChartProps({ data, layout }, false, colorMap, isDarkTheme)}
legendProps={legendProps}
/>
);
}
return <VerticalStackedBarChart {...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme)} />;
return (
<VerticalStackedBarChart
{...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
/>
);
case 'heatmap':
return <HeatMapChart {...transformPlotlyJsonToHeatmapProps(plotlySchema)} />;
return <HeatMapChart {...transformPlotlyJsonToHeatmapProps(plotlySchema)} legendProps={legendProps} />;
case 'sankey':
return <SankeyChart {...transformPlotlyJsonToSankeyProps(plotlySchema, colorMap, isDarkTheme)} />;
case 'indicator':
if (plotlySchema?.data?.[0]?.mode?.includes('gauge')) {
return <GaugeChart {...transformPlotlyJsonToGaugeProps(plotlySchema, colorMap, isDarkTheme)} />;
if (data?.[0]?.mode?.includes('gauge')) {
return (
<GaugeChart
{...transformPlotlyJsonToGaugeProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
/>
);
}
return <div>Unsupported Schema</div>;
case 'histogram':
return <VerticalBarChart {...transformPlotlyJsonToVBCProps(plotlySchema, colorMap, isDarkTheme)} />;
return (
<VerticalBarChart
{...transformPlotlyJsonToVBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
/>
);
default:
return <div>Unsupported Schema</div>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -537,9 +536,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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,16 +195,6 @@ export class LegendsBase extends React.Component<ILegendsProps, ILegendState> {
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;
};

AtishayMsft marked this conversation as resolved.
Show resolved Hide resolved
/**
* Get the new selected legends based on the legend that was clicked when multi-select is enabled.
* @param legend The legend that was clicked
Expand Down Expand Up @@ -242,9 +232,7 @@ export class LegendsBase extends React.Component<ILegendsProps, ILegendState> {
? 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?.();
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
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';

interface IDeclarativeChartState {
selectedChoice: string;
preSelectLegends: boolean;
selectedLegends: string;
}

const options: IDropdownOption[] = [
Expand Down Expand Up @@ -39,6 +42,8 @@ export class DeclarativeChartBasicExample extends React.Component<{}, IDeclarati
super(props);
this.state = {
selectedChoice: 'donutchart',
preSelectLegends: false,
selectedLegends: '',
};
}

Expand All @@ -47,7 +52,16 @@ export class DeclarativeChartBasicExample extends React.Component<{}, IDeclarati
}

private _onChange = (ev: React.FormEvent<HTMLInputElement>, option: IDropdownOption): void => {
this.setState({ selectedChoice: option.key as string });
this.setState({ selectedChoice: option.key as string, selectedLegends: '' });
};

private _onTogglePreselectLegends = (ev: React.MouseEvent<HTMLElement>, checked: boolean) => {
this.setState({ preSelectLegends: checked });
};

private _handleChartSchemaChanged = (eventData: Schema) => {
const { selectedLegends } = eventData.plotlySchema;
this.setState({ selectedLegends: selectedLegends.join(', ') });
};

private _getSchemaByKey(key: string): any {
Expand All @@ -57,20 +71,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 (
<>
<Dropdown
label="Select a schema"
options={options}
onChange={this._onChange}
selectedKey={this.state.selectedChoice}
styles={dropdownStyles}
/>
<div style={{ display: 'flex' }}>
<Dropdown
label="Select a schema"
options={options}
onChange={this._onChange}
selectedKey={this.state.selectedChoice}
styles={dropdownStyles}
/>
&nbsp;&nbsp;&nbsp;
<Toggle
label="Pre select legends"
onText="ON"
offText="OFF"
onChange={this._onTogglePreselectLegends}
checked={this.state.preSelectLegends}
/>
</div>
<br />
<br />
<DeclarativeChart key={uniqueKey} chartSchema={inputSchema} onSchemaChange={this._handleChartSchemaChanged} />
<br />
<DeclarativeChart chartSchema={inputSchema} />
Legend selection changed : {this.state.selectedLegends}
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,6 @@
"plot_bgcolor": "#F5F6F9",
"paper_bgcolor": "#F5F6F9"
},
"frames": []
"frames": [],
"selectedLegends": ["a"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,6 @@
"hovermode": "closest",
"showlegend": true
},
"frames": []
"frames": [],
"selectedLegends": ["Cadillac"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,6 @@
"plot_bgcolor": "#F5F6F9",
"paper_bgcolor": "#F5F6F9"
},
"frames": []
"frames": [],
"selectedLegends": ["Trace 0"]
}
Loading