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

feat(react-charting): strongly type plotly schema and bug fixes #33621

Merged
merged 23 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0419eac
Make plotly schema strongly typed
AtishayMsft Jan 12, 2025
d36c6be
Remove explicit type checking
AtishayMsft Jan 12, 2025
437be6d
Fix compilation errors
AtishayMsft Jan 13, 2025
ab36c15
Fix lint errors
AtishayMsft Jan 13, 2025
ddaa435
Update snapshot
AtishayMsft Jan 13, 2025
ffa1da0
Add change file
AtishayMsft Jan 13, 2025
6b9432e
Sankey and gauge chart fix (#33628)
Anush2303 Jan 13, 2025
0e0d631
Refactor code
AtishayMsft Jan 15, 2025
1dde2d9
Refactor legend naming
AtishayMsft Jan 15, 2025
4ac87d0
Adding secondary y axis for declarative charts (#33625)
srmukher Jan 15, 2025
54d722b
Adding fallback and fixes for test app crashes for 12 schema data (#…
srmukher Jan 16, 2025
b301ec9
Merge branch 'master' into user/atisjai/stronglyTypePlotly
AtishayMsft Jan 16, 2025
2d1d3bc
Refactor to remove duplicate code
AtishayMsft Jan 17, 2025
03455a4
fix(react-charting): resolve bugs in declarative chart (#33564)
krkshitij Jan 17, 2025
fac7280
fix(declarative charts): Add check in Date parsing method (#33674)
Anush2303 Jan 17, 2025
379df8e
Fix gauge value label
atisjai Jan 18, 2025
5309685
feat(declarative-chart): Add default colorScale in heatmap chart (#33…
Anush2303 Jan 20, 2025
e6f03f8
fix: [BUG 12161] handle scatter plot with markers mode in plotly (#33…
srmukher Jan 20, 2025
3e4fdb7
fix: [BUG 12159] default legend selection for Horizontal Bar Chart (#…
srmukher Jan 20, 2025
047c115
Merge master
atisjai Jan 20, 2025
f90a4a1
Merge branch 'user/atisjai/stronglyTypePlotly' of https://github.com/…
atisjai Jan 20, 2025
465a1cd
Format error
atisjai Jan 20, 2025
a19d189
Update snapshot
atisjai Jan 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Adding fallback and fixes for test app crashes for 12 schema data ",
"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": "Use strongly typed interfaces for plotly schema",
"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": "fix: resolve bugs in declarative 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": "StronglyType plotly schema bug fix",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { DeclarativeChart } from '@fluentui/react-charting';
AtishayMsft marked this conversation as resolved.
Show resolved Hide resolved

console.log(DeclarativeChart);

export default {
name: 'DeclarativeChart',
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as React from 'react';
import { useTheme } from '@fluentui/react';
import { IRefObject } from '@fluentui/react/lib/Utilities';
import { DonutChart } from '../DonutChart/index';
import { VerticalStackedBarChart } from '../VerticalStackedBarChart/index';
import { PlotData, PlotlySchema } from './PlotlySchema';
//import type { Data, Layout } from './PlotlySchema';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this comment.

import {
isArrayOrTypedArray,
isDateArray,
Expand All @@ -21,10 +22,11 @@ import {
transformPlotlyJsonToGaugeProps,
transformPlotlyJsonToGVBCProps,
transformPlotlyJsonToVBCProps,
isLineData,
} from './PlotlySchemaAdapter';
import { LineChart } from '../LineChart/index';
import { LineChart, ILineChartProps } from '../LineChart/index';
import { HorizontalBarChartWithAxis } from '../HorizontalBarChartWithAxis/index';
import { AreaChart } from '../AreaChart/index';
import { AreaChart, IAreaChartProps } from '../AreaChart/index';
import { HeatMapChart } from '../HeatMapChart/index';
import { SankeyChart } from '../SankeyChart/SankeyChart';
import { GaugeChart } from '../GaugeChart/index';
Expand All @@ -41,6 +43,7 @@ export interface Schema {
/**
* Plotly schema represented as JSON object
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
plotlySchema: any;
}

Expand Down Expand Up @@ -87,12 +90,8 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
DeclarativeChartProps
>((props, forwardedRef) => {
const { plotlySchema } = sanitizeJson(props.chartSchema);
const { data, layout } = plotlySchema;
const plotlyInput = plotlySchema as 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;
Expand All @@ -106,7 +105,7 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
const onActiveLegendsChange = (keys: string[]) => {
setActiveLegends(keys);
if (props.onSchemaChange) {
props.onSchemaChange({ plotlySchema: { data, layout, selectedLegends: keys } });
props.onSchemaChange({ plotlySchema: { plotlyInput, selectedLegends: keys } });
}
};

Expand All @@ -118,16 +117,69 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
setActiveLegends(selectedLegends ?? []);
}, [props.chartSchema]);

const legendProps = {
canSelectMultipleLegends: false,
const multiSelectLegendProps = {
canSelectMultipleLegends: true,
onChange: onActiveLegendsChange,
selectedLegend: activeLegends.slice(0, 1)[0],
selectedLegends: activeLegends,
};

const commonProps = {
legendProps: multiSelectLegendProps,
componentRef: chartRef,
calloutProps: { layerProps: { eventBubblingEnabled: true } },
};

const checkAndRenderChart = (
renderChartJsx: (chartProps: ILineChartProps | IAreaChartProps) => JSX.Element,
isAreaChart: boolean = false,
) => {
let fallbackVSBC = false;
const xValues = (plotlyInput.data[0] as PlotData).x;
const isXDate = isDateArray(xValues);
const isXNumber = isNumberArray(xValues);
const isXMonth = isMonthArray(xValues);
if (isXDate || isXNumber) {
const chartProps: ILineChartProps | IAreaChartProps = {
...transformPlotlyJsonToScatterChartProps(
{ data: plotlyInput.data, layout: plotlyInput.layout },
isAreaChart,
colorMap,
isDarkTheme,
),
...commonProps,
};
return renderChartJsx(chartProps);
} else if (isXMonth) {
const updatedData = plotlyInput.data.map((dataPoint: PlotData) => ({
...dataPoint,
x: updateXValues(dataPoint.x),
}));
const chartProps: ILineChartProps | IAreaChartProps = {
...transformPlotlyJsonToScatterChartProps(
{ data: updatedData, layout: plotlyInput.layout },
isAreaChart,
colorMap,
isDarkTheme,
),
...commonProps,
};
return renderChartJsx(chartProps);
}
// Unsupported schema, render as VerticalStackedBarChart
fallbackVSBC = true;
return (
<VerticalStackedBarChart
{...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme, fallbackVSBC)}
{...commonProps}
/>
);
};

const exportAsImage = React.useCallback(
(opts?: IImageExportOptions) => {
return toImage(chartRef.current?.chartContainer, {
background: theme.semanticColors.bodyBackground,
scale: 3,
...opts,
});
},
Expand All @@ -142,146 +194,78 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
[exportAsImage],
);

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

switch (data[0].type) {
switch (plotlyInput.data[0].type) {
case 'pie':
return (
<DonutChart
{...transformPlotlyJsonToDonutProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
// Bubble event to prevent right click to open menu on the callout
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
return <DonutChart {...transformPlotlyJsonToDonutProps(plotlySchema, colorMap, isDarkTheme)} {...commonProps} />;
case 'bar':
const orientation = data[0].orientation;
const orientation = plotlyInput.data[0].orientation;
if (orientation === 'h') {
return (
<HorizontalBarChartWithAxis
{...transformPlotlyJsonToHorizontalBarWithAxisProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
{...commonProps}
/>
);
} else {
if (['group', 'overlay'].includes(plotlySchema?.layout?.barmode)) {
const containsLines = plotlyInput.data.some(
series => series.type === 'scatter' || isLineData(series as Partial<PlotData>),
);
if (['group', 'overlay'].includes(plotlySchema?.layout?.barmode) && !containsLines) {
return (
<GroupedVerticalBarChart
{...transformPlotlyJsonToGVBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
{...commonProps}
/>
);
}
return (
<VerticalStackedBarChart
{...transformPlotlyJsonToVSBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
{...commonProps}
/>
);
}
case 'scatter':
const isAreaChart = data.some((series: any) => series.fill === 'tonexty' || series.fill === 'tozeroy');
const renderChart = (chartProps: any) => {
if (plotlyInput.data[0].mode === 'markers') {
throw new Error(`Unsupported chart - type :${plotlyInput.data[0]?.type}, mode: ${plotlyInput.data[0]?.mode}`);
}
const isAreaChart = plotlyInput.data.some(
(series: PlotData) => series.fill === 'tonexty' || series.fill === 'tozeroy',
);
const renderChartJsx = (chartProps: ILineChartProps | IAreaChartProps) => {
if (isAreaChart) {
return (
<AreaChart
{...chartProps}
legendProps={{ ...legendProps, canSelectMultipleLegends: true, selectedLegends: activeLegends }}
/>
);
return <AreaChart {...chartProps} />;
}
return (
<LineChart
{...{
...chartProps,
legendProps: {
onChange: onActiveLegendsChange,
canSelectMultipleLegends: true,
selectedLegends: activeLegends,
},
}}
/>
);
return <LineChart {...chartProps} />;
};
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={multiSelectLegendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
return checkAndRenderChart(renderChartJsx, isAreaChart);
case 'heatmap':
return (
<HeatMapChart
{...transformPlotlyJsonToHeatmapProps(plotlySchema)}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
);
return <HeatMapChart {...transformPlotlyJsonToHeatmapProps(plotlySchema)} {...commonProps} legendProps={{}} />;
case 'sankey':
return (
<SankeyChart
{...transformPlotlyJsonToSankeyProps(plotlySchema, colorMap, isDarkTheme)}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
<SankeyChart {...transformPlotlyJsonToSankeyProps(plotlySchema, colorMap, isDarkTheme)} {...commonProps} />
);
case 'indicator':
if (data?.[0]?.mode?.includes('gauge')) {
if (plotlyInput.data?.[0]?.mode?.includes('gauge')) {
return (
<GaugeChart
{...transformPlotlyJsonToGaugeProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
<GaugeChart {...transformPlotlyJsonToGaugeProps(plotlySchema, colorMap, isDarkTheme)} {...commonProps} />
);
}
return <div>Unsupported Schema</div>;
throw new Error(`Unsupported chart - type: ${plotlyInput.data[0]?.type}, mode: ${plotlyInput.data[0]?.mode}`);
case 'histogram':
return (
<VerticalBarChart
{...transformPlotlyJsonToVBCProps(plotlySchema, colorMap, isDarkTheme)}
legendProps={multiSelectLegendProps}
componentRef={chartRef}
calloutProps={{ layerProps: { eventBubblingEnabled: true } }}
/>
<VerticalBarChart {...transformPlotlyJsonToVBCProps(plotlySchema, colorMap, isDarkTheme)} {...commonProps} />
);
default:
throw new Error('Unsupported chart schema');
const xValues = (plotlyInput.data[0] as PlotData).x;
const yValues = (plotlyInput.data[0] as PlotData).y;
if (xValues && yValues && xValues.length > 0 && yValues.length > 0) {
const renderLineChartJsx = (chartProps: ILineChartProps) => {
return <LineChart {...chartProps} />;
};
return checkAndRenderChart(renderLineChartJsx);
}
throw new Error(`Unsupported chart type :${plotlyInput.data[0]?.type}`);
}
});
DeclarativeChart.displayName = 'DeclarativeChart';
Loading
Loading