From 556fc8e7b9a921cedbb07c2f0670dfabcf8ec0ed Mon Sep 17 00:00:00 2001 From: srmukher <120183316+srmukher@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:02:05 +0530 Subject: [PATCH] Enable 'tozeroy' mode for Area Charts (#33581) --- ...-34dd50d3-d7cd-4354-9e9b-b8eeb44d4e19.json | 7 + .../react-charting/etc/react-charting.api.md | 1 + .../components/AreaChart/AreaChart.base.tsx | 91 +- .../components/AreaChart/AreaChart.types.ts | 6 + .../AreaChart/AreaChartRTL.test.tsx | 10 + .../__snapshots__/AreaChartRTL.test.tsx.snap | 1142 +++++++++++++++++ .../DeclarativeChart/PlotlySchemaAdapter.ts | 3 + .../PlotlySchemaAdapterUT.test.tsx.snap | 2 + .../AreaChart/AreaChart.Basic.Example.tsx | 19 +- 9 files changed, 1245 insertions(+), 36 deletions(-) create mode 100644 change/@fluentui-react-charting-34dd50d3-d7cd-4354-9e9b-b8eeb44d4e19.json diff --git a/change/@fluentui-react-charting-34dd50d3-d7cd-4354-9e9b-b8eeb44d4e19.json b/change/@fluentui-react-charting-34dd50d3-d7cd-4354-9e9b-b8eeb44d4e19.json new file mode 100644 index 0000000000000..c5392850b4598 --- /dev/null +++ b/change/@fluentui-react-charting-34dd50d3-d7cd-4354-9e9b-b8eeb44d4e19.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Support tozeroy mode for Area Charts", + "packageName": "@fluentui/react-charting", + "email": "120183316+srmukher@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/charts/react-charting/etc/react-charting.api.md b/packages/charts/react-charting/etc/react-charting.api.md index 3d1c49be07983..207610b95c35a 100644 --- a/packages/charts/react-charting/etc/react-charting.api.md +++ b/packages/charts/react-charting/etc/react-charting.api.md @@ -200,6 +200,7 @@ export interface IAreaChartProps extends ICartesianChartProps { data: IChartProps; enableGradient?: boolean; enablePerfOptimization?: boolean; + mode?: 'tozeroy' | 'tonexty'; onRenderCalloutPerDataPoint?: IRenderFunction; onRenderCalloutPerStack?: IRenderFunction; // (undocumented) diff --git a/packages/charts/react-charting/src/components/AreaChart/AreaChart.base.tsx b/packages/charts/react-charting/src/components/AreaChart/AreaChart.base.tsx index 02933ecb66672..d4411e51c3d67 100644 --- a/packages/charts/react-charting/src/components/AreaChart/AreaChart.base.tsx +++ b/packages/charts/react-charting/src/components/AreaChart/AreaChart.base.tsx @@ -64,7 +64,7 @@ export interface IAreaChartAreaPoint { values: IAreaChartDataSetPoint; } export interface IAreaChartDataSetPoint { - [key: string]: number | string; + [key: string]: number | string | number[]; } export interface IDPointType { values: { 0: number; 1: number; data: {} }; @@ -102,7 +102,7 @@ export class AreaChartBase extends React.Component { - const stackedValues = d3Stack().keys(keys)(dataSet); - const maxOfYVal = d3Max(stackedValues[stackedValues.length - 1], dp => dp[1])!; - const stackedData: Array = []; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - stackedValues.forEach((layer: any) => { - const currentStack: IAreaChartDataSetPoint[] = []; + private _getDataPoints = (keys: string[], dataSet: any) => { + const renderPoints: Array = []; + let maxOfYVal = 0; + + if (this.props.mode === 'tozeroy') { + keys.forEach((key, index) => { + const currentLayer: IAreaChartDataSetPoint[] = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dataSet.forEach((d: any) => { + currentLayer.push({ + values: [0, d[key]], // Start from zero for "tozeroy" mode + xVal: d.xVal, + }); + if (d[key] > maxOfYVal) { + maxOfYVal = d[key]; + } + }); + renderPoints.push(currentLayer); + }); + } else { + const dataValues = d3Stack().keys(keys)(dataSet); + maxOfYVal = d3Max(dataValues[dataValues.length - 1], dp => dp[1])!; // eslint-disable-next-line @typescript-eslint/no-explicit-any - layer.forEach((d: any) => { - currentStack.push({ - values: d, - xVal: d.data.xVal, + dataValues.forEach((layer: any) => { + const currentLayer: IAreaChartDataSetPoint[] = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + layer.forEach((d: any) => { + currentLayer.push({ + values: d, + xVal: d.data.xVal, + }); }); + renderPoints.push(currentLayer); }); - stackedData.push(currentStack); - }); + } + this._isMultiStackChart = !!(this.props.legendProps?.selectedLegends - ? stackedData?.length >= 1 - : stackedData?.length > 1); + ? renderPoints?.length >= 1 + : renderPoints?.length > 1); return { - stackedData, + renderData: renderPoints, maxOfYVal, }; }; @@ -498,7 +518,7 @@ export class AreaChartBase extends React.Component, index: number) => { + this._data.forEach((singleStackedData: Array, index: number) => { + const layerOpacity = this.props.mode === 'tozeroy' ? 0.8 : this._opacity[index]; graph.push( {this.props.enableGradient && ( @@ -757,7 +778,7 @@ export class AreaChartBase extends React.Component, index: number) => { + this._data.forEach((singleStackedData: Array, index: number) => { if (points.length === index) { return; } @@ -806,7 +827,7 @@ export class AreaChartBase extends React.Component {singleStackedData.map((singlePoint: IDPointType, pointIndex: number) => { - const circleId = `${this._circleId}_${index * this._stackedData[0].length + pointIndex}`; + const circleId = `${this._circleId}_${index * this._data[0].length + pointIndex}`; const xDataPoint = singlePoint.xVal instanceof Date ? singlePoint.xVal.getTime() : singlePoint.xVal; lineColor = points[index]!.color!; const legend = points[index]!.legend; @@ -839,7 +860,7 @@ export class AreaChartBase extends React.Component { const xDataPoint = singlePoint.xVal instanceof Date ? singlePoint.xVal.getTime() : singlePoint.xVal; if (this.state.nearestCircleToHighlight === xDataPoint) { - const circleId = `${this._circleId}_${index * this._stackedData[0].length + pointIndex}`; + const circleId = `${this._circleId}_${index * this._data[0].length + pointIndex}`; lineColor = points[index]!.color!; const legend = points[index]!.legend; graph.push( diff --git a/packages/charts/react-charting/src/components/AreaChart/AreaChart.types.ts b/packages/charts/react-charting/src/components/AreaChart/AreaChart.types.ts index f0aa8a6e77eaf..b0ff53bf52f3c 100644 --- a/packages/charts/react-charting/src/components/AreaChart/AreaChart.types.ts +++ b/packages/charts/react-charting/src/components/AreaChart/AreaChart.types.ts @@ -70,6 +70,12 @@ export interface IAreaChartProps extends ICartesianChartProps { * The prop used to enable gradient fill color for the chart. */ enableGradient?: boolean; + + /** + * @default tonexty + * The prop used to define the Y axis mode (tonexty or tozeroy) + */ + mode?: 'tozeroy' | 'tonexty'; } /** diff --git a/packages/charts/react-charting/src/components/AreaChart/AreaChartRTL.test.tsx b/packages/charts/react-charting/src/components/AreaChart/AreaChartRTL.test.tsx index f054dd4a1ceb7..12977fac2432d 100644 --- a/packages/charts/react-charting/src/components/AreaChart/AreaChartRTL.test.tsx +++ b/packages/charts/react-charting/src/components/AreaChart/AreaChartRTL.test.tsx @@ -512,6 +512,16 @@ describe('Area chart rendering', () => { expect(container).toMatchSnapshot(); }, ); + + testWithoutWait( + 'Should render the Area Chart with tozeroy mode', + AreaChart, + { data: chartData, mode: 'tozeroy' }, + container => { + //Asset + expect(container).toMatchSnapshot(); + }, + ); }); describe('Area chart - Subcomponent Area', () => { diff --git a/packages/charts/react-charting/src/components/AreaChart/__snapshots__/AreaChartRTL.test.tsx.snap b/packages/charts/react-charting/src/components/AreaChart/__snapshots__/AreaChartRTL.test.tsx.snap index 83e1e7b66fa1e..d151a2c1ec458 100644 --- a/packages/charts/react-charting/src/components/AreaChart/__snapshots__/AreaChartRTL.test.tsx.snap +++ b/packages/charts/react-charting/src/components/AreaChart/__snapshots__/AreaChartRTL.test.tsx.snap @@ -1142,6 +1142,1148 @@ exports[`Area chart rendering Should render the Area Chart with negative y value `; +exports[`Area chart rendering Should render the Area Chart with tozeroy mode 1`] = ` +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+`; + exports[`Area chart rendering Should render the Area chart with date x-axis data when tick Values given and tick format is %d 1`] = `
{ const { data, layout } = jsonObj; + let mode: string = 'tonexty'; const chartData: ILineChartPoints[] = data.map((series: any, index: number) => { const xValues = series.x; @@ -352,6 +353,7 @@ export const transformPlotlyJsonToScatterChartProps = ( const isXNumber = isNumberArray(xValues); const legend: string = series.name || `Series ${index + 1}`; const lineColor = getColor(legend, colorMap, isDarkTheme); + mode = series.fill === 'tozeroy' ? 'tozeroy' : 'tonexty'; return { legend, @@ -376,6 +378,7 @@ export const transformPlotlyJsonToScatterChartProps = ( supportNegativeData: true, xAxisTitle, yAxisTitle, + mode, } as IAreaChartProps; } else { return { diff --git a/packages/charts/react-charting/src/components/DeclarativeChart/__snapshots__/PlotlySchemaAdapterUT.test.tsx.snap b/packages/charts/react-charting/src/components/DeclarativeChart/__snapshots__/PlotlySchemaAdapterUT.test.tsx.snap index 8966e3f55d1b9..85baf67c9f352 100644 --- a/packages/charts/react-charting/src/components/DeclarativeChart/__snapshots__/PlotlySchemaAdapterUT.test.tsx.snap +++ b/packages/charts/react-charting/src/components/DeclarativeChart/__snapshots__/PlotlySchemaAdapterUT.test.tsx.snap @@ -3324,6 +3324,7 @@ Object { }, ], }, + "mode": "tonexty", "supportNegativeData": true, "xAxisTitle": "", "yAxisTitle": "", @@ -4555,6 +4556,7 @@ Object { }, ], }, + "mode": "tonexty", "supportNegativeData": true, "xAxisTitle": "", "yAxisTitle": "", diff --git a/packages/react-examples/src/react-charting/AreaChart/AreaChart.Basic.Example.tsx b/packages/react-examples/src/react-charting/AreaChart/AreaChart.Basic.Example.tsx index 387535181da0e..1c65e5376cb2a 100644 --- a/packages/react-examples/src/react-charting/AreaChart/AreaChart.Basic.Example.tsx +++ b/packages/react-examples/src/react-charting/AreaChart/AreaChart.Basic.Example.tsx @@ -10,6 +10,7 @@ interface IAreaChartBasicState { isCalloutselected: boolean; showAxisTitles: boolean; legendMultiSelect: boolean; + changeChartMode: boolean; } const options: IChoiceGroupOption[] = [ @@ -26,6 +27,7 @@ export class AreaChartBasicExample extends React.Component<{}, IAreaChartBasicSt isCalloutselected: false, showAxisTitles: true, legendMultiSelect: false, + changeChartMode: false, }; } public componentDidMount(): void { @@ -75,6 +77,11 @@ export class AreaChartBasicExample extends React.Component<{}, IAreaChartBasicSt this.setState({ legendMultiSelect: checked }); }; + private _onToggleChartMode = (ev: React.MouseEvent, checked: boolean) => { + this.forceUpdate(); + this.setState({ changeChartMode: checked }); + }; + private _basicExample(): JSX.Element { const chart1Points = [ { @@ -181,7 +188,7 @@ export class AreaChartBasicExample extends React.Component<{}, IAreaChartBasicSt const chart3Points = chart1Points.map((point, index) => { return { x: point.x, - y: point.y - 5000, + y: point.y + 7000, xAxisCalloutData: point.xAxisCalloutData, yAxisCalloutData: point.yAxisCalloutData, }; @@ -249,6 +256,14 @@ export class AreaChartBasicExample extends React.Component<{}, IAreaChartBasicSt onChange={this._onToggleLegendMultiSelect} styles={{ root: { marginTop: '10px' } }} /> + {this.state.showAxisTitles && (
)} @@ -300,6 +316,7 @@ export class AreaChartBasicExample extends React.Component<{}, IAreaChartBasicSt legendProps={{ canSelectMultipleLegends: this.state.legendMultiSelect, }} + mode={this.state.changeChartMode ? 'tozeroy' : 'tonexty'} />
)}