From 1fde19c3e5a7ea3270f424d24ccb8735af78c65f Mon Sep 17 00:00:00 2001 From: Anush Date: Tue, 18 Nov 2025 16:16:53 +0530 Subject: [PATCH 1/4] remove duplicate padding issue --- .../react-charts/library/etc/react-charts.api.md | 1 + .../CommonComponents/CartesianChart.types.ts | 5 +++++ .../library/src/utilities/utilities.ts | 15 +++++++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/charts/react-charts/library/etc/react-charts.api.md b/packages/charts/react-charts/library/etc/react-charts.api.md index a84062e5582b14..d7324ac709eecc 100644 --- a/packages/charts/react-charts/library/etc/react-charts.api.md +++ b/packages/charts/react-charts/library/etc/react-charts.api.md @@ -244,6 +244,7 @@ export interface CartesianChartProps { xAxistickSize?: number; xAxisTitle?: string; xMaxValue?: number; + xMinValue?: number; xScaleType?: AxisScaleType; yAxis?: AxisProps; yAxisAnnotation?: string; diff --git a/packages/charts/react-charts/library/src/components/CommonComponents/CartesianChart.types.ts b/packages/charts/react-charts/library/src/components/CommonComponents/CartesianChart.types.ts index ca0dee904f53c4..7a3207069c469a 100644 --- a/packages/charts/react-charts/library/src/components/CommonComponents/CartesianChart.types.ts +++ b/packages/charts/react-charts/library/src/components/CommonComponents/CartesianChart.types.ts @@ -272,6 +272,11 @@ export interface CartesianChartProps { */ yMaxValue?: number; + /** + * minimum data value point in x-axis (for numeric x-axis) + */ + xMinValue?: number; + /** * maximum data value point in x-axis */ diff --git a/packages/charts/react-charts/library/src/utilities/utilities.ts b/packages/charts/react-charts/library/src/utilities/utilities.ts index d4ab833d6b146f..e22bcf9be87d8f 100644 --- a/packages/charts/react-charts/library/src/utilities/utilities.ts +++ b/packages/charts/react-charts/library/src/utilities/utilities.ts @@ -1360,12 +1360,13 @@ export function domainRangeOfNumericForAreaLineScatterCharts( isRTL: boolean, scaleType?: AxisScaleType, hasMarkersMode?: boolean, + xMinVal?: number, ): IDomainNRange { const isScatterPolar = isScatterPolarSeries(points); let [xMin, xMax] = getScatterXDomainExtent(points, scaleType) as [number, number]; if (hasMarkersMode) { - const xPadding = getDomainPaddingForMarkers(xMin, xMax, scaleType); + const xPadding = getDomainPaddingForMarkers(xMin, xMax, scaleType, xMinVal); xMin = xMin - xPadding.start; xMax = xMax + xPadding.end; } @@ -2206,6 +2207,7 @@ export const getDomainPaddingForMarkers = ( minVal: number, maxVal: number, scaleType?: AxisScaleType, + xMinVal?: number, ): { start: number; end: number } => { if (scaleType === 'log') { return { @@ -2214,7 +2216,16 @@ export const getDomainPaddingForMarkers = ( }; } - const defaultPadding = (maxVal - minVal) * 0.1; + /* if user explicitly sets xMinVal, we will check that to avoid excessive padding on the min side. + if the difference between minVal and xMinVal is more than 10% of the data range, we set padding to 0 on that side + this is to avoid cases where xMinVal is significantly smaller than minVal, which would lead to excessive padding. + In other cases, we apply the default 10% padding on both sides. + */ + const defaultPadding = xMinVal + ? (maxVal - minVal) * 0.1 > Math.abs(minVal - Math.min(minVal, xMinVal!)) + ? 0 + : (maxVal - minVal) * 0.1 + : (maxVal - minVal) * 0.1; return { start: defaultPadding, end: defaultPadding, From a4055b1ff2cac66fab278ebc708a2890fd9ade39 Mon Sep 17 00:00:00 2001 From: Anush Date: Tue, 18 Nov 2025 16:18:33 +0530 Subject: [PATCH 2/4] Add change file --- ...-react-charts-e3e811b0-ea5f-41d5-997c-1ac37f1f5c81.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-charts-e3e811b0-ea5f-41d5-997c-1ac37f1f5c81.json diff --git a/change/@fluentui-react-charts-e3e811b0-ea5f-41d5-997c-1ac37f1f5c81.json b/change/@fluentui-react-charts-e3e811b0-ea5f-41d5-997c-1ac37f1f5c81.json new file mode 100644 index 00000000000000..8a10dc47e2cc11 --- /dev/null +++ b/change/@fluentui-react-charts-e3e811b0-ea5f-41d5-997c-1ac37f1f5c81.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix double padding issue in scatter chart", + "packageName": "@fluentui/react-charts", + "email": "anushgupta@microsoft.com", + "dependentChangeType": "patch" +} From e91e1cc6e074e5e776f859986438401713a3dd9c Mon Sep 17 00:00:00 2001 From: Anush Date: Thu, 20 Nov 2025 14:29:08 +0530 Subject: [PATCH 3/4] refine logic --- .../src/components/LineChart/LineChart.tsx | 4 +++ .../components/ScatterChart/ScatterChart.tsx | 4 +++ .../library/src/utilities/utilities.ts | 30 ++++++++++++------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/charts/react-charts/library/src/components/LineChart/LineChart.tsx b/packages/charts/react-charts/library/src/components/LineChart/LineChart.tsx index 0ceee9469a8cf5..fc54ba434ab1cc 100644 --- a/packages/charts/react-charts/library/src/components/LineChart/LineChart.tsx +++ b/packages/charts/react-charts/library/src/components/LineChart/LineChart.tsx @@ -234,6 +234,8 @@ export const LineChart: React.FunctionComponent = React.forwardR isRTL, props.xScaleType, _hasMarkersMode, + props.xMinValue, + props.xMaxValue, ); } else if (xAxisType === XAxisTypes.DateAxis) { domainNRangeValue = domainRangeOfDateForAreaLineScatterVerticalBarCharts( @@ -550,6 +552,8 @@ export const LineChart: React.FunctionComponent = React.forwardR xScaleType: props.xScaleType, yScaleType: props.yScaleType, secondaryYScaleType: props.secondaryYScaleType, + xMinValue: props.xMinValue, + xMaxValue: props.xMaxValue, }) : 0; if (_points[i].data.length === 1) { diff --git a/packages/charts/react-charts/library/src/components/ScatterChart/ScatterChart.tsx b/packages/charts/react-charts/library/src/components/ScatterChart/ScatterChart.tsx index 6355aca0aa541d..0dc8419522caa0 100644 --- a/packages/charts/react-charts/library/src/components/ScatterChart/ScatterChart.tsx +++ b/packages/charts/react-charts/library/src/components/ScatterChart/ScatterChart.tsx @@ -210,6 +210,8 @@ export const ScatterChart: React.FunctionComponent = React.fo isRTL, props.xScaleType, true, + props.xMinValue, + props.xMaxValue, ); } else if (xAxisType === XAxisTypes.DateAxis) { domainNRangeValue = domainRangeOfDateForAreaLineScatterVerticalBarCharts( @@ -386,6 +388,8 @@ export const ScatterChart: React.FunctionComponent = React.fo yScalePrimary: _yAxisScale, xScaleType: props.xScaleType, yScaleType: props.yScaleType, + xMinValue: props.xMinValue, + xMaxValue: props.xMaxValue, }) : 0; diff --git a/packages/charts/react-charts/library/src/utilities/utilities.ts b/packages/charts/react-charts/library/src/utilities/utilities.ts index 515b0208f5a549..2766086990dac4 100644 --- a/packages/charts/react-charts/library/src/utilities/utilities.ts +++ b/packages/charts/react-charts/library/src/utilities/utilities.ts @@ -1367,12 +1367,13 @@ export function domainRangeOfNumericForAreaLineScatterCharts( scaleType?: AxisScaleType, hasMarkersMode?: boolean, xMinVal?: number, + xMaxVal?: number, ): IDomainNRange { const isScatterPolar = isScatterPolarSeries(points); let [xMin, xMax] = getScatterXDomainExtent(points, scaleType) as [number, number]; if (hasMarkersMode) { - const xPadding = getDomainPaddingForMarkers(xMin, xMax, scaleType, xMinVal); + const xPadding = getDomainPaddingForMarkers(xMin, xMax, scaleType, xMinVal, xMaxVal); xMin = xMin - xPadding.start; xMax = xMax + xPadding.end; } @@ -2214,6 +2215,7 @@ export const getDomainPaddingForMarkers = ( maxVal: number, scaleType?: AxisScaleType, xMinVal?: number, + xMaxVal?: number, ): { start: number; end: number } => { if (scaleType === 'log') { return { @@ -2222,16 +2224,18 @@ export const getDomainPaddingForMarkers = ( }; } - /* if user explicitly sets xMinVal, we will check that to avoid excessive padding on the min side. - if the difference between minVal and xMinVal is more than 10% of the data range, we set padding to 0 on that side - this is to avoid cases where xMinVal is significantly smaller than minVal, which would lead to excessive padding. - In other cases, we apply the default 10% padding on both sides. + /* if user explicitly sets xMinVal or xMaxVal, we will check that to avoid excessive padding on either side. + If the difference between minVal and xMinVal is more than 10% of the data range, we set padding to 0 on that side. + this is to avoid cases where xMinVal is significantly smaller than minVal or xMaxVal is significantly larger than + maxVal, which would lead to excessive padding. In other cases, we apply the default 10% padding on both sides. */ - const defaultPadding = xMinVal - ? (maxVal - minVal) * 0.1 > Math.abs(minVal - Math.min(minVal, xMinVal!)) - ? 0 - : (maxVal - minVal) * 0.1 - : (maxVal - minVal) * 0.1; + const rangePadding = (maxVal - minVal) * 0.1; + + // If explicit bounds are set and they're far from the data range, don't add extra padding + const hasExcessivePaddingAtMin = xMinVal !== undefined && rangePadding > Math.abs(minVal - Math.min(minVal, xMinVal)); + const hasExcessivePaddingAtMax = xMaxVal !== undefined && rangePadding > Math.abs(maxVal - Math.max(maxVal, xMaxVal)); + + const defaultPadding = hasExcessivePaddingAtMin && hasExcessivePaddingAtMax ? 0 : rangePadding; return { start: defaultPadding, end: defaultPadding, @@ -2281,6 +2285,8 @@ export const getRangeForScatterMarkerSize = ({ xScaleType, yScaleType: primaryYScaleType, secondaryYScaleType, + xMinValue, + xMaxValue, }: { data: LineChartPoints[] | ScatterChartPoints[]; xScale: ScaleContinuousNumeric | ScaleTime; @@ -2290,6 +2296,8 @@ export const getRangeForScatterMarkerSize = ({ xScaleType?: AxisScaleType; yScaleType?: AxisScaleType; secondaryYScaleType?: AxisScaleType; + xMinValue?: number; + xMaxValue?: number; }): number => { // Note: This function is executed after the scale is created, so the actual padding can be // obtained by calculating the difference between the respective minimums or maximums of the @@ -2301,7 +2309,7 @@ export const getRangeForScatterMarkerSize = ({ // it the other way around (i.e., adjusting the scale domain first with padding and then scaling // the markers to fit inside the plot area). const [xMin, xMax] = getScatterXDomainExtent(data, xScaleType); - const xPadding = getDomainPaddingForMarkers(+xMin, +xMax, xScaleType); + const xPadding = getDomainPaddingForMarkers(+xMin, +xMax, xScaleType, xMinValue, xMaxValue); const scaleXMin = xMin instanceof Date ? new Date(+xMin - xPadding.start) : xMin - xPadding.start; const scaleXMax = xMax instanceof Date ? new Date(+xMax + xPadding.end) : xMax + xPadding.end; const extraXPixels = Math.min(Math.abs(xScale(xMin) - xScale(scaleXMin)), Math.abs(xScale(scaleXMax) - xScale(xMax))); From 4d38410c6e318f6b789e55e49610863a1348f887 Mon Sep 17 00:00:00 2001 From: Anush Date: Thu, 20 Nov 2025 15:47:55 +0530 Subject: [PATCH 4/4] handle double padding for y axis also --- .../src/components/LineChart/LineChart.tsx | 2 ++ .../components/ScatterChart/ScatterChart.tsx | 2 ++ .../library/src/utilities/utilities.ts | 22 ++++++++++++------- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/charts/react-charts/library/src/components/LineChart/LineChart.tsx b/packages/charts/react-charts/library/src/components/LineChart/LineChart.tsx index fc54ba434ab1cc..86650bd531700a 100644 --- a/packages/charts/react-charts/library/src/components/LineChart/LineChart.tsx +++ b/packages/charts/react-charts/library/src/components/LineChart/LineChart.tsx @@ -554,6 +554,8 @@ export const LineChart: React.FunctionComponent = React.forwardR secondaryYScaleType: props.secondaryYScaleType, xMinValue: props.xMinValue, xMaxValue: props.xMaxValue, + yMinValue: props.yMinValue, + yMaxValue: props.yMaxValue, }) : 0; if (_points[i].data.length === 1) { diff --git a/packages/charts/react-charts/library/src/components/ScatterChart/ScatterChart.tsx b/packages/charts/react-charts/library/src/components/ScatterChart/ScatterChart.tsx index 0dc8419522caa0..9a944c938940a3 100644 --- a/packages/charts/react-charts/library/src/components/ScatterChart/ScatterChart.tsx +++ b/packages/charts/react-charts/library/src/components/ScatterChart/ScatterChart.tsx @@ -390,6 +390,8 @@ export const ScatterChart: React.FunctionComponent = React.fo yScaleType: props.yScaleType, xMinValue: props.xMinValue, xMaxValue: props.xMaxValue, + yMinValue: props.yMinValue, + yMaxValue: props.yMaxValue, }) : 0; diff --git a/packages/charts/react-charts/library/src/utilities/utilities.ts b/packages/charts/react-charts/library/src/utilities/utilities.ts index 2766086990dac4..bdbe0944530656 100644 --- a/packages/charts/react-charts/library/src/utilities/utilities.ts +++ b/packages/charts/react-charts/library/src/utilities/utilities.ts @@ -2214,8 +2214,8 @@ export const getDomainPaddingForMarkers = ( minVal: number, maxVal: number, scaleType?: AxisScaleType, - xMinVal?: number, - xMaxVal?: number, + userMinVal?: number, + userMaxVal?: number, ): { start: number; end: number } => { if (scaleType === 'log') { return { @@ -2224,16 +2224,18 @@ export const getDomainPaddingForMarkers = ( }; } - /* if user explicitly sets xMinVal or xMaxVal, we will check that to avoid excessive padding on either side. - If the difference between minVal and xMinVal is more than 10% of the data range, we set padding to 0 on that side. - this is to avoid cases where xMinVal is significantly smaller than minVal or xMaxVal is significantly larger than + /* if user explicitly sets userMinVal or userMaxVal, we will check that to avoid excessive padding on either side. + If the difference between minVal and userMinVal is more than 10% of the data range, we set padding to 0 on that side. + this is to avoid cases where userMinVal is significantly smaller than minVal or userMaxVal is significantly larger than maxVal, which would lead to excessive padding. In other cases, we apply the default 10% padding on both sides. */ const rangePadding = (maxVal - minVal) * 0.1; // If explicit bounds are set and they're far from the data range, don't add extra padding - const hasExcessivePaddingAtMin = xMinVal !== undefined && rangePadding > Math.abs(minVal - Math.min(minVal, xMinVal)); - const hasExcessivePaddingAtMax = xMaxVal !== undefined && rangePadding > Math.abs(maxVal - Math.max(maxVal, xMaxVal)); + const hasExcessivePaddingAtMin = + userMinVal !== undefined && rangePadding > Math.abs(minVal - Math.min(minVal, userMinVal)); + const hasExcessivePaddingAtMax = + userMaxVal !== undefined && rangePadding > Math.abs(maxVal - Math.max(maxVal, userMaxVal)); const defaultPadding = hasExcessivePaddingAtMin && hasExcessivePaddingAtMax ? 0 : rangePadding; return { @@ -2287,6 +2289,8 @@ export const getRangeForScatterMarkerSize = ({ secondaryYScaleType, xMinValue, xMaxValue, + yMinValue, + yMaxValue, }: { data: LineChartPoints[] | ScatterChartPoints[]; xScale: ScaleContinuousNumeric | ScaleTime; @@ -2298,6 +2302,8 @@ export const getRangeForScatterMarkerSize = ({ secondaryYScaleType?: AxisScaleType; xMinValue?: number; xMaxValue?: number; + yMinValue?: number; + yMaxValue?: number; }): number => { // Note: This function is executed after the scale is created, so the actual padding can be // obtained by calculating the difference between the respective minimums or maximums of the @@ -2316,7 +2322,7 @@ export const getRangeForScatterMarkerSize = ({ const yScaleType = useSecondaryYScale ? secondaryYScaleType : primaryYScaleType; const { startValue: yMin, endValue: yMax } = findNumericMinMaxOfY(data, undefined, useSecondaryYScale, yScaleType); - const yPadding = getDomainPaddingForMarkers(yMin, yMax, yScaleType); + const yPadding = getDomainPaddingForMarkers(yMin, yMax, yScaleType, yMinValue, yMaxValue); const scaleYMin = yMin - yPadding.start; const scaleYMax = yMax + yPadding.end; const yScale = (useSecondaryYScale ? yScaleSecondary : yScalePrimary)!;