Skip to content

Commit 2d64a43

Browse files
Anush2303Anush
andauthored
fix(charts): fix chart 983 crash (#35474)
Co-authored-by: Anush <anushgupta@microsoft.com>
1 parent 970b07b commit 2d64a43

File tree

4 files changed

+143
-62
lines changed

4 files changed

+143
-62
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "fix chart 983 crash",
4+
"packageName": "@fluentui/react-charting",
5+
"email": "anushgupta@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "add reference lines logic in line and scatter chart",
4+
"packageName": "@fluentui/react-charts",
5+
"email": "anushgupta@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/charts/react-charting/src/components/DeclarativeChart/PlotlySchemaAdapter.ts

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
ISankeyChartData,
2727
ILineChartLineOptions,
2828
IGanttChartDataPoint,
29+
IScatterChartPoints,
2930
} from '../../types/IDataPoint';
3031
import { ISankeyChartProps } from '../SankeyChart/index';
3132
import { IVerticalStackedBarChartProps } from '../VerticalStackedBarChart/index';
@@ -362,6 +363,32 @@ export const resolveXAxisPoint = (
362363
return x;
363364
};
364365

366+
/**
367+
* Extracts unique X-axis categories from Plotly data traces
368+
* @param data Array of Plotly data traces
369+
* @returns Array of unique x values
370+
*/
371+
const extractXCategories = (data: Data[] | undefined): Datum[] => {
372+
return Array.from(
373+
new Set(
374+
(data ?? [])
375+
.flatMap(trace => {
376+
const xData = (trace as Partial<PlotData>).x;
377+
if (!xData) {
378+
return [];
379+
}
380+
if (Array.isArray(xData)) {
381+
return xData.flat().map(x => {
382+
return x;
383+
});
384+
}
385+
return [];
386+
})
387+
.filter(x => x !== undefined && x !== null),
388+
),
389+
);
390+
};
391+
365392
/**
366393
* Checks if a key should be ignored during normalization
367394
* @param key The key to check
@@ -1374,15 +1401,19 @@ export const transformPlotlyJsonToVSBCProps = (
13741401
});
13751402
});
13761403
});
1377-
const xCategories = (input.data[0] as Partial<PlotData>)?.x ?? [];
1404+
const xCategories = extractXCategories(input.data);
13781405

13791406
(input.layout?.shapes ?? [])
13801407
.filter(shape => shape.type === 'line')
13811408
.forEach((shape, shapeIdx) => {
13821409
const lineColor = shape.line?.color;
13831410
const resolveX = (val: Datum) => {
1384-
if (typeof val === 'number' && Array.isArray(xCategories) && xCategories[val] !== undefined) {
1385-
return xCategories[val];
1411+
if (typeof val === 'number' && Array.isArray(xCategories)) {
1412+
if (xCategories[val] !== undefined) {
1413+
return xCategories[val];
1414+
} else {
1415+
return xCategories[shapeIdx];
1416+
}
13861417
}
13871418
return val;
13881419
};
@@ -1407,20 +1438,26 @@ export const transformPlotlyJsonToVSBCProps = (
14071438

14081439
const y0Val = resolveY(shape.y0!);
14091440
const y1Val = resolveY(shape.y1!);
1410-
mapXToDataPoints[x0Key as string].lineData!.push({
1411-
legend: `Reference_${shapeIdx}`,
1412-
y: y0Val as string,
1413-
color: rgb(lineColor!).formatHex8() ?? lineColor,
1414-
lineOptions: getLineOptions(shape.line),
1415-
useSecondaryYScale: false,
1416-
});
1417-
mapXToDataPoints[x1Key as string].lineData!.push({
1418-
legend: `Reference_${shapeIdx}`,
1419-
y: y1Val as string,
1420-
color: rgb(lineColor!).formatHex8() ?? lineColor,
1421-
lineOptions: getLineOptions(shape.line),
1422-
useSecondaryYScale: false,
1423-
});
1441+
1442+
if (mapXToDataPoints[x0Key as string]) {
1443+
mapXToDataPoints[x0Key as string].lineData!.push({
1444+
legend: `Reference_${shapeIdx}`,
1445+
y: y0Val as string,
1446+
color: rgb(lineColor!).formatHex8() ?? lineColor,
1447+
lineOptions: getLineOptions(shape.line),
1448+
useSecondaryYScale: false,
1449+
});
1450+
}
1451+
1452+
if (mapXToDataPoints[x1Key as string]) {
1453+
mapXToDataPoints[x1Key as string].lineData!.push({
1454+
legend: `Reference_${shapeIdx}`,
1455+
y: y1Val as string,
1456+
color: rgb(lineColor!).formatHex8() ?? lineColor,
1457+
lineOptions: getLineOptions(shape.line),
1458+
useSecondaryYScale: false,
1459+
});
1460+
}
14241461
});
14251462

14261463
const vsbcData = Object.values(mapXToDataPoints);
@@ -1934,12 +1971,20 @@ const transformPlotlyJsonToScatterTraceProps = (
19341971
const xMaxValue = chartData[0]?.data[chartData[0].data.length - 1]?.x;
19351972
const yMinValue = chartData[0]?.data[0]?.y;
19361973
const yMaxValue = chartData[0]?.data[chartData[0].data.length - 1]?.y;
1937-
const lineShape: ILineChartPoints[] = (input.layout?.shapes ?? [])
1974+
const xCategories = extractXCategories(input.data);
1975+
const lineShape: ILineChartPoints[] | IScatterChartPoints[] = (input.layout?.shapes ?? [])
19381976
.filter(shape => shape.type === 'line')
19391977
.map((shape, shapeIdx) => {
19401978
const lineColor = shape.line?.color;
19411979

19421980
const resolveX = (val: Datum) => {
1981+
if (typeof val === 'number' && Array.isArray(xCategories)) {
1982+
if (xCategories[val] !== undefined) {
1983+
return xCategories[val];
1984+
} else {
1985+
return xCategories[shapeIdx];
1986+
}
1987+
}
19431988
if (shape.xref === 'paper') {
19441989
if (val === 0) {
19451990
return xMinValue;
@@ -1973,13 +2018,13 @@ const transformPlotlyJsonToScatterTraceProps = (
19732018
return {
19742019
legend: `Reference_${shapeIdx}`,
19752020
data: [
1976-
{ x: resolveX(shape.x0!), y: resolveY(shape.y0!) },
1977-
{ x: resolveX(shape.x1!), y: resolveY(shape.y1!) },
2021+
{ x: resolveXValue(resolveX(shape.x0!)), y: resolveY(shape.y0!) },
2022+
{ x: resolveXValue(resolveX(shape.x1!)), y: resolveY(shape.y1!) },
19782023
],
19792024
color: rgb(lineColor!).formatHex8() ?? lineColor,
19802025
lineOptions: getLineOptions(shape.line),
19812026
useSecondaryYScale: false,
1982-
} as ILineChartPoints;
2027+
} as ILineChartPoints | IScatterChartPoints;
19832028
});
19842029

19852030
const yMinMax = getYMinMaxValues(input.data[0], input.layout);
@@ -1992,11 +2037,11 @@ const transformPlotlyJsonToScatterTraceProps = (
19922037
const numDataPoints = chartData.reduce((total, lineChartPoints) => total + lineChartPoints.data.length, 0);
19932038

19942039
const chartProps: IChartProps = {
1995-
lineChartData: [...chartData, ...lineShape],
2040+
lineChartData: [...chartData, ...(lineShape as ILineChartPoints[])],
19962041
};
19972042

19982043
const scatterChartProps: IChartProps = {
1999-
scatterChartData: chartData,
2044+
scatterChartData: [...chartData, ...(lineShape as IScatterChartPoints[])],
20002045
};
20012046

20022047
const annotations = getChartAnnotationsFromLayout(input.layout, input.data, isMultiPlot);

packages/charts/react-charts/library/src/components/DeclarativeChart/PlotlySchemaAdapter.ts

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
SankeyChartData,
2929
LineChartLineOptions,
3030
GanttChartDataPoint,
31+
ScatterChartPoints,
3132
} from '../../types/DataPoint';
3233
import { SankeyChartProps } from '../SankeyChart/index';
3334
import { VerticalStackedBarChartProps } from '../VerticalStackedBarChart/index';
@@ -362,6 +363,32 @@ export const resolveXAxisPoint = (
362363
return x;
363364
};
364365

366+
/**
367+
* Extracts unique X-axis categories from Plotly data traces
368+
* @param data Array of Plotly data traces
369+
* @returns Array of unique x values
370+
*/
371+
const extractXCategories = (data: Data[] | undefined): Datum[] => {
372+
return Array.from(
373+
new Set(
374+
(data ?? [])
375+
.flatMap(trace => {
376+
const xData = (trace as Partial<PlotData>).x;
377+
if (!xData) {
378+
return [];
379+
}
380+
if (Array.isArray(xData)) {
381+
return xData.flat().map(x => {
382+
return x;
383+
});
384+
}
385+
return [];
386+
})
387+
.filter(x => x !== undefined && x !== null),
388+
),
389+
);
390+
};
391+
365392
/**
366393
* Checks if a key should be ignored during normalization
367394
* @param key The key to check
@@ -1372,24 +1399,7 @@ export const transformPlotlyJsonToVSBCProps = (
13721399
});
13731400
});
13741401

1375-
const xCategories = Array.from(
1376-
new Set(
1377-
(input.data ?? [])
1378-
.flatMap(trace => {
1379-
const xData = (trace as Partial<PlotData>).x;
1380-
if (!xData) {
1381-
return [];
1382-
}
1383-
if (Array.isArray(xData)) {
1384-
return xData.flat().map(x => {
1385-
return x;
1386-
});
1387-
}
1388-
return [];
1389-
})
1390-
.filter(x => x !== undefined && x !== null),
1391-
),
1392-
);
1402+
const xCategories = extractXCategories(input.data);
13931403

13941404
(input.layout?.shapes ?? [])
13951405
.filter(shape => shape.type === 'line')
@@ -1434,20 +1444,25 @@ export const transformPlotlyJsonToVSBCProps = (
14341444

14351445
const y0Val = resolveY(shape.y0!);
14361446
const y1Val = resolveY(shape.y1!);
1437-
mapXToDataPoints[x0Key as string].lineData!.push({
1438-
legend: `Reference_${shapeIdx}`,
1439-
y: y0Val as string,
1440-
color: rgb(lineColor!).formatHex8() ?? lineColor,
1441-
lineOptions: getLineOptions(shape.line),
1442-
useSecondaryYScale: false,
1443-
});
1444-
mapXToDataPoints[x1Key as string].lineData!.push({
1445-
legend: `Reference_${shapeIdx}`,
1446-
y: y1Val as string,
1447-
color: rgb(lineColor!).formatHex8() ?? lineColor,
1448-
lineOptions: getLineOptions(shape.line),
1449-
useSecondaryYScale: false,
1450-
});
1447+
if (mapXToDataPoints[x0Key as string]) {
1448+
mapXToDataPoints[x0Key as string].lineData!.push({
1449+
legend: `Reference_${shapeIdx}`,
1450+
y: y0Val as string,
1451+
color: rgb(lineColor!).formatHex8() ?? lineColor,
1452+
lineOptions: getLineOptions(shape.line),
1453+
useSecondaryYScale: false,
1454+
});
1455+
}
1456+
1457+
if (mapXToDataPoints[x1Key as string]) {
1458+
mapXToDataPoints[x1Key as string].lineData!.push({
1459+
legend: `Reference_${shapeIdx}`,
1460+
y: y1Val as string,
1461+
color: rgb(lineColor!).formatHex8() ?? lineColor,
1462+
lineOptions: getLineOptions(shape.line),
1463+
useSecondaryYScale: false,
1464+
});
1465+
}
14511466
});
14521467

14531468
const vsbcData = Object.values(mapXToDataPoints);
@@ -1950,12 +1965,19 @@ const transformPlotlyJsonToScatterTraceProps = (
19501965
const xMaxValue = chartData[0]?.data[chartData[0].data.length - 1]?.x;
19511966
const yMinValue = chartData[0]?.data[0]?.y;
19521967
const yMaxValue = chartData[0]?.data[chartData[0].data.length - 1]?.y;
1953-
1954-
const lineShape: LineChartPoints[] = (input.layout?.shapes ?? [])
1968+
const xCategories = extractXCategories(input.data);
1969+
const lineShape: LineChartPoints[] | ScatterChartPoints[] = (input.layout?.shapes ?? [])
19551970
.filter(shape => shape.type === 'line')
19561971
.map((shape, shapeIdx) => {
19571972
const lineColor = shape.line?.color;
19581973
const resolveX = (val: Datum) => {
1974+
if (typeof val === 'number' && Array.isArray(xCategories)) {
1975+
if (xCategories[val] !== undefined) {
1976+
return xCategories[val];
1977+
} else {
1978+
return xCategories[shapeIdx];
1979+
}
1980+
}
19591981
if (shape.xref === 'paper') {
19601982
if (val === 0) {
19611983
return xMinValue;
@@ -1990,13 +2012,13 @@ const transformPlotlyJsonToScatterTraceProps = (
19902012
return {
19912013
legend: `Reference_${shapeIdx}`,
19922014
data: [
1993-
{ x: resolveX(shape.x0!), y: resolveY(shape.y0!) },
1994-
{ x: resolveX(shape.x1!), y: resolveY(shape.y1!) },
2015+
{ x: resolveXValue(resolveX(shape.x0!)), y: resolveY(shape.y0!) },
2016+
{ x: resolveXValue(resolveX(shape.x1!)), y: resolveY(shape.y1!) },
19952017
],
19962018
color: rgb(lineColor!).formatHex8() ?? lineColor,
19972019
lineOptions: getLineOptions(shape.line),
19982020
useSecondaryYScale: false,
1999-
} as LineChartPoints;
2021+
} as LineChartPoints | ScatterChartPoints;
20002022
});
20012023

20022024
const yMinMax = getYMinMaxValues(input.data[0], input.layout);
@@ -2008,11 +2030,11 @@ const transformPlotlyJsonToScatterTraceProps = (
20082030
const numDataPoints = chartData.reduce((total, lineChartPoints) => total + lineChartPoints.data.length, 0);
20092031

20102032
const chartProps: ChartProps = {
2011-
lineChartData: [...chartData, ...lineShape],
2033+
lineChartData: [...chartData, ...(lineShape as LineChartPoints[])],
20122034
};
20132035

20142036
const scatterChartProps: ChartProps = {
2015-
scatterChartData: chartData,
2037+
scatterChartData: [...chartData, ...(lineShape as ScatterChartPoints[])],
20162038
};
20172039

20182040
const annotations = getChartAnnotationsFromLayout(input.layout, input.data, isMultiPlot);

0 commit comments

Comments
 (0)