Skip to content

Commit

Permalink
Merge branch 'master' into users/srmukher/multiLegendVBC
Browse files Browse the repository at this point in the history
  • Loading branch information
srmukher authored Dec 27, 2024
2 parents c21cde4 + 99cbe5c commit cd93832
Show file tree
Hide file tree
Showing 30 changed files with 942 additions and 157 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Support changing legends programatically at runtime and bug fixes",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Full Yaxis labels in HeatMap chart",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Support for multiple legend selection for Vertical Stacked Bar Chart ",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "plotly examples bug fixes",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Enabling multiple legend selection for Grouped Vertical Bar Chart",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
2 changes: 2 additions & 0 deletions packages/charts/react-charting/etc/react-charting.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@ export interface IHeatMapChartProps extends Pick<ICartesianChartProps, Exclude<k
domainValuesForColorScale: number[];
legendProps?: Partial<ILegendsProps>;
rangeValuesForColorScale: string[];
showYAxisLables?: boolean;
styles?: IStyleFunctionOrObject<IHeatMapChartStyleProps, IHeatMapChartStyles>;
xAxisDateFormatString?: string;
xAxisNumberFormatString?: string;
Expand Down Expand Up @@ -1228,6 +1229,7 @@ export interface ISankeyChartData {
export interface ISankeyChartProps {
accessibility?: ISankeyChartAccessibilityProps;
borderColorsForNodes?: string[];
calloutProps?: Partial<ICalloutProps>;
className?: string;
colorsForNodes?: string[];
componentRef?: IRefObject<IChart>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,13 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
this._cartesianChartRef = React.createRef();
}

public componentDidUpdate() {
public componentDidUpdate(prevProps: IAreaChartProps): void {
if (prevProps.legendProps?.selectedLegend !== this.props.legendProps?.selectedLegend) {
this.setState({
selectedLegend: this.props.legendProps?.selectedLegend ?? '',
});
}

if (this.state.isShowCalloutPending) {
this.setState({
refSelected: `#${this._highlightedCircleId}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
IModifiedCartesianChartProps,
IYValueHover,
IHorizontalBarChartWithAxisDataPoint,
IHeatMapChartDataPoint,
} from '../../index';
import { convertToLocaleString } from '../../utilities/locale-util';
import {
Expand Down Expand Up @@ -38,6 +39,7 @@ const getClassNames = classNamesFunction<ICartesianChartStyleProps, ICartesianCh
const ChartHoverCard = React.lazy(() =>
import('../../utilities/ChartHoverCard/ChartHoverCard').then(module => ({ default: module.ChartHoverCard })),
);
const chartTypesToCheck = [ChartTypes.HorizontalBarChartWithAxis, ChartTypes.HeatMapChart];

export interface ICartesianChartState {
containerWidth: number;
Expand Down Expand Up @@ -141,14 +143,11 @@ export class CartesianChartBase

public componentDidMount(): void {
this._fitParentContainer();
if (
this.props.chartType === ChartTypes.HorizontalBarChartWithAxis &&
this.props.showYAxisLables &&
this.yAxisElement
) {
const maxYAxisLabelLength = calculateLongestLabelWidth(
this.props.points.map((point: IHorizontalBarChartWithAxisDataPoint) => point.y),
`.${this._classNames.yAxis} text`,
if (chartTypesToCheck.includes(this.props.chartType) && this.props.showYAxisLables && this.yAxisElement) {
const maxYAxisLabelLength = this.calculateMaxYAxisLabelLength(
this.props.chartType,
this.props.points,
this._classNames.yAxis!,
);
if (this.state.startFromX !== maxYAxisLabelLength) {
this.setState({
Expand Down Expand Up @@ -194,14 +193,11 @@ export class CartesianChartBase
});
}
}
if (
this.props.chartType === ChartTypes.HorizontalBarChartWithAxis &&
this.props.showYAxisLables &&
this.yAxisElement
) {
const maxYAxisLabelLength = calculateLongestLabelWidth(
this.props.points.map((point: IHorizontalBarChartWithAxisDataPoint) => point.y),
`.${this._classNames.yAxis} text`,
if (chartTypesToCheck.includes(this.props.chartType) && this.props.showYAxisLables && this.yAxisElement) {
const maxYAxisLabelLength = this.calculateMaxYAxisLabelLength(
this.props.chartType,
this.props.points,
this._classNames.yAxis!,
);
if (this.state.startFromX !== maxYAxisLabelLength) {
this.setState({
Expand All @@ -220,6 +216,25 @@ export class CartesianChartBase
}
}

public calculateMaxYAxisLabelLength = (
chartType: ChartTypes,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
points: any[],
className: string,
): number => {
if (chartType === ChartTypes.HeatMapChart) {
return calculateLongestLabelWidth(
points[0].data.map((point: IHeatMapChartDataPoint) => point.y),
`.${className} text`,
);
} else {
return calculateLongestLabelWidth(
points.map((point: IHorizontalBarChartWithAxisDataPoint) => point.y),
`.${className} text`,
);
}
};

public render(): JSX.Element {
const {
calloutProps,
Expand All @@ -238,7 +253,7 @@ export class CartesianChartBase
}

const margin = { ...this.margins };
if (this.props.chartType === ChartTypes.HorizontalBarChartWithAxis) {
if (chartTypesToCheck.includes(this.props.chartType)) {
if (!this._isRtl) {
margin.left! += this.state.startFromX;
} else {
Expand Down Expand Up @@ -415,7 +430,7 @@ export class CartesianChartBase
truncating the rest of the text and showing elipsis
or showing the whole string,
* */
this.props.chartType === ChartTypes.HorizontalBarChartWithAxis &&
chartTypesToCheck.includes(this.props.chartType) &&
yScale &&
createYAxisLabels(
this.yAxisElement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ import { IRefObject } from '@fluentui/react/lib/Utilities';
import { DonutChart } from '../DonutChart/index';
import { VerticalStackedBarChart } from '../VerticalStackedBarChart/index';
import {
isArrayOrTypedArray,
isDateArray,
isNumberArray,
isMonthArray,
sanitizeJson,
updateXValues,
transformPlotlyJsonToDonutProps,
transformPlotlyJsonToVSBCProps,
transformPlotlyJsonToScatterChartProps,
transformPlotlyJsonToHorizontalBarWithAxisProps,
isDateArray,
isNumberArray,
transformPlotlyJsonToHeatmapProps,
transformPlotlyJsonToSankeyProps,
transformPlotlyJsonToGaugeProps,
Expand Down Expand Up @@ -82,28 +86,42 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
HTMLDivElement,
DeclarativeChartProps
>((props, forwardedRef) => {
const { plotlySchema } = props.chartSchema;
const { data, layout, selectedLegends } = plotlySchema;
const { plotlySchema } = sanitizeJson(props.chartSchema);
const { data, layout } = plotlySchema;
let { selectedLegends } = plotlySchema;
const xValues = data[0].x;
const isXDate = isDateArray(xValues);
const isXNumber = isNumberArray(xValues);
const isXMonth = isMonthArray(xValues);
const colorMap = useColorMapping();
const theme = useTheme();
const isDarkTheme = theme?.isInverted ?? false;
const chartRef = React.useRef<IChart>(null);

const [activeLegends, setActiveLegends] = React.useState<string[]>(selectedLegends ?? []);
if (!isArrayOrTypedArray(selectedLegends)) {
selectedLegends = [];
}

const [activeLegends, setActiveLegends] = React.useState<string[]>(selectedLegends);
const onActiveLegendsChange = (keys: string[]) => {
setActiveLegends(keys);
if (props.onSchemaChange) {
props.onSchemaChange({ plotlySchema: { data, layout, selectedLegends: keys } });
}
};

React.useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-shadow
const { plotlySchema } = sanitizeJson(props.chartSchema);
// eslint-disable-next-line @typescript-eslint/no-shadow
const { selectedLegends } = plotlySchema;
setActiveLegends(selectedLegends ?? []);
}, [props.chartSchema]);

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

const exportAsImage = React.useCallback(
Expand All @@ -129,8 +147,10 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
return (
<DonutChart
{...transformPlotlyJsonToDonutProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={{ ...legendProps, canSelectMultipleLegends: true }}
legendProps={{ ...legendProps, canSelectMultipleLegends: true, selectedLegends: activeLegends }}
componentRef={chartRef}
// Bubble event to prevent right click to open menu on the callout
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
case 'bar':
Expand All @@ -141,55 +161,75 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
{...transformPlotlyJsonToHorizontalBarWithAxisProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
} else {
if (['group', 'overlay'].includes(plotlySchema?.layout?.barmode)) {
return (
<GroupedVerticalBarChart
{...transformPlotlyJsonToGVBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
legendProps={{ ...legendProps, canSelectMultipleLegends: true, selectedLegends: activeLegends }}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
}
return (
<VerticalStackedBarChart
{...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
legendProps={{ ...legendProps, canSelectMultipleLegends: true, selectedLegends: activeLegends }}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
}
case 'scatter':
const isAreaChart = data.some((series: any) => series.fill === 'tonexty' || series.fill === 'tozeroy');
if (isXDate || isXNumber) {
const renderChart = (chartProps: any) => {
if (isAreaChart) {
return (
<AreaChart
{...transformPlotlyJsonToScatterChartProps({ data, layout }, true, colorMap, isDarkTheme)}
legendProps={legendProps}
componentRef={chartRef}
/>
);
return <AreaChart {...chartProps} />;
}
return (
<LineChart
{...transformPlotlyJsonToScatterChartProps({ data, layout }, false, colorMap, isDarkTheme)}
legendProps={{
onChange: onActiveLegendsChange,
canSelectMultipleLegends: true,
selectedLegends: activeLegends,
{...{
...chartProps,
legendProps: {
onChange: onActiveLegendsChange,
canSelectMultipleLegends: true,
selectedLegends: activeLegends,
},
}}
componentRef={chartRef}
/>
);
};
if (isXDate || isXNumber) {
const chartProps = {
...transformPlotlyJsonToScatterChartProps({ data, layout }, isAreaChart, colorMap, isDarkTheme),
legendProps,
componentRef: chartRef,
calloutProps: { layerProps: { eventBubblingEnabled: true } },
};
return renderChart(chartProps);
} else if (isXMonth) {
const updatedData = data.map((dataPoint: any) => ({
...dataPoint,
x: updateXValues(dataPoint.x),
}));
const chartProps = {
...transformPlotlyJsonToScatterChartProps({ data: updatedData, layout }, isAreaChart, colorMap, isDarkTheme),
legendProps,
componentRef: chartRef,
calloutProps: { layerProps: { eventBubblingEnabled: true } },
};
return renderChart(chartProps);
}
return (
<VerticalStackedBarChart
{...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
legendProps={{ ...legendProps, canSelectMultipleLegends: true, selectedLegends: activeLegends }}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
case 'heatmap':
Expand All @@ -198,13 +238,15 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
{...transformPlotlyJsonToHeatmapProps(plotlySchema)}
legendProps={legendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
case 'sankey':
return (
<SankeyChart
{...transformPlotlyJsonToSankeyProps(plotlySchema, colorMap, isDarkTheme)}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
case 'indicator':
Expand All @@ -214,6 +256,7 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
{...transformPlotlyJsonToGaugeProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
}
Expand All @@ -224,10 +267,11 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
{...transformPlotlyJsonToVBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={legendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
default:
return <div>Unsupported Schema</div>;
throw new Error('Unsupported chart schema');
}
});
DeclarativeChart.displayName = 'DeclarativeChart';
Loading

0 comments on commit cd93832

Please sign in to comment.