Skip to content

Commit

Permalink
Enable 'tozeroy' mode for Area Charts (#33581)
Browse files Browse the repository at this point in the history
  • Loading branch information
srmukher authored Jan 17, 2025
1 parent fd420e1 commit 556fc8e
Show file tree
Hide file tree
Showing 9 changed files with 1,245 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Support tozeroy mode for Area Charts",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions packages/charts/react-charting/etc/react-charting.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export interface IAreaChartProps extends ICartesianChartProps {
data: IChartProps;
enableGradient?: boolean;
enablePerfOptimization?: boolean;
mode?: 'tozeroy' | 'tonexty';
onRenderCalloutPerDataPoint?: IRenderFunction<ICustomizedCalloutData>;
onRenderCalloutPerStack?: IRenderFunction<ICustomizedCalloutData>;
// (undocumented)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {} };
Expand Down Expand Up @@ -102,7 +102,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
colors: string[];
opacity: number[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
stackedInfo: any;
data: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
calloutPoints: any;
};
Expand All @@ -113,7 +113,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
private _circleId: string;
private _uniqueCallOutID: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _stackedData: any;
private _data: any;
private _chart: JSX.Element[];
private margins: IMargins;
private _rectId: string;
Expand Down Expand Up @@ -184,12 +184,12 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
if (!this._isChartEmpty()) {
const { lineChartData } = this.props.data;
const points = this._addDefaultColors(lineChartData);
const { colors, opacity, stackedInfo, calloutPoints } = this._createSet(points);
const { colors, opacity, data, calloutPoints } = this._createSet(points);
this._calloutPoints = calloutPoints;
const isXAxisDateType = getXAxisType(points);
this._colors = colors;
this._opacity = opacity;
this._stackedData = stackedInfo.stackedData;
this._data = data.renderData;
const legends: JSX.Element = this._getLegendData(points);

const tickParams = {
Expand Down Expand Up @@ -223,7 +223,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
isCalloutForStack
xAxisType={isXAxisDateType ? XAxisTypes.DateAxis : XAxisTypes.NumericAxis}
tickParams={tickParams}
maxOfYVal={stackedInfo.maxOfYVal}
maxOfYVal={data.maxOfYVal}
getGraphData={this._getGraphData}
getDomainNRangeValues={this._getDomainNRangeValues}
createStringYAxis={createStringYAxis}
Expand Down Expand Up @@ -434,27 +434,47 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _getStackedData = (keys: string[], dataSet: any) => {
const stackedValues = d3Stack().keys(keys)(dataSet);
const maxOfYVal = d3Max(stackedValues[stackedValues.length - 1], dp => dp[1])!;
const stackedData: Array<IAreaChartDataSetPoint[]> = [];
// 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<IAreaChartDataSetPoint[]> = [];
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,
};
};
Expand Down Expand Up @@ -498,22 +518,22 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
dataSet.push(singleDataset);
});

// get keys from dataset, used to create stacked data
// get keys from dataset, used to render data
const keysLength: number = dataSet && Object.keys(dataSet[0])!.length;
const keys: string[] = [];
for (let i = 0; i < keysLength - 1; i++) {
const keyVal = `chart${i}`;
keys.push(keyVal);
}

// Stacked Info used to draw graph
const stackedInfo = this._getStackedData(keys, dataSet);
// Data used to draw graph
const data = this._getDataPoints(keys, dataSet);

return {
colors,
opacity,
keys,
stackedInfo,
data,
calloutPoints,
};
} else {
Expand Down Expand Up @@ -559,14 +579,14 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
keys.push(keyVal);
}

// Stacked Info used to draw graph
const stackedInfo = this._getStackedData(keys, dataSet);
// Data used to draw graph
const data = this._getDataPoints(keys, dataSet);

return {
colors,
opacity,
keys,
stackedInfo,
data,
calloutPoints,
};
}
Expand Down Expand Up @@ -723,7 +743,8 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
const graph: JSX.Element[] = [];
let lineColor: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this._stackedData.forEach((singleStackedData: Array<any>, index: number) => {
this._data.forEach((singleStackedData: Array<any>, index: number) => {
const layerOpacity = this.props.mode === 'tozeroy' ? 0.8 : this._opacity[index];
graph.push(
<React.Fragment key={`${index}-graph-${this._uniqueIdForGraph}`}>
{this.props.enableGradient && (
Expand Down Expand Up @@ -757,7 +778,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
stroke={this._colors[index]}
strokeWidth={3}
fill={this._colors[index]}
opacity={this._opacity[index]}
opacity={layerOpacity}
fillOpacity={this._getOpacity(points[index]!.legend)}
onMouseMove={this._onRectMouseMove}
onMouseOut={this._onRectMouseOut}
Expand All @@ -768,7 +789,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
id={`${index}-graph-${this._uniqueIdForGraph}`}
d={area(singleStackedData)!}
fill={this.props.enableGradient ? `url(#gradient_${index})` : this._colors[index]}
opacity={this._opacity[index]}
opacity={layerOpacity}
fillOpacity={this._getOpacity(points[index]!.legend)}
onMouseMove={this._onRectMouseMove}
onMouseOut={this._onRectMouseOut}
Expand All @@ -788,7 +809,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt

const circleRadius = pointOptions && pointOptions.r ? Number(pointOptions.r) : 8;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this._stackedData.forEach((singleStackedData: Array<any>, index: number) => {
this._data.forEach((singleStackedData: Array<any>, index: number) => {
if (points.length === index) {
return;
}
Expand All @@ -806,7 +827,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
} data points.`}
>
{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;
Expand Down Expand Up @@ -839,7 +860,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
singleStackedData.forEach((singlePoint: IDPointType, pointIndex: number) => {
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Loading

0 comments on commit 556fc8e

Please sign in to comment.