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

Enable 'tozeroy' mode for Area Charts #33581

Merged
merged 13 commits into from
Jan 17, 2025
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
srmukher marked this conversation as resolved.
Show resolved Hide resolved
srmukher marked this conversation as resolved.
Show resolved Hide resolved
srmukher marked this conversation as resolved.
Show resolved Hide resolved
srmukher marked this conversation as resolved.
Show resolved Hide resolved
"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 {
srmukher marked this conversation as resolved.
Show resolved Hide resolved
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)
srmukher marked this conversation as resolved.
Show resolved Hide resolved
*/
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
Loading