diff --git a/projects/js-packages/charts/changelog/charts-164-fix-chart-height-and-size-calculations-for-pie-chart-and b/projects/js-packages/charts/changelog/charts-164-fix-chart-height-and-size-calculations-for-pie-chart-and
new file mode 100644
index 00000000000..6978eb2f293
--- /dev/null
+++ b/projects/js-packages/charts/changelog/charts-164-fix-chart-height-and-size-calculations-for-pie-chart-and
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Charts: fix chart height and size calculations for Pie Chart and variants.
diff --git a/projects/js-packages/charts/src/charts/bar-chart/bar-chart.tsx b/projects/js-packages/charts/src/charts/bar-chart/bar-chart.tsx
index d77cce9eaed..80ba480c0b1 100644
--- a/projects/js-packages/charts/src/charts/bar-chart/bar-chart.tsx
+++ b/projects/js-packages/charts/src/charts/bar-chart/bar-chart.tsx
@@ -12,7 +12,7 @@ import {
useChartDataTransform,
useZeroValueDisplay,
useChartMargin,
- useElementHeight,
+ useElementSize,
useHasLegendChild,
usePrefersReducedMotion,
} from '../../hooks';
@@ -125,7 +125,7 @@ const BarChartInternal: FC< BarChartProps > = ( {
const legendItems = useChartLegendItems( dataSorted );
const chartOptions = useBarChartOptions( dataWithVisibleZeros, horizontal, options );
const defaultMargin = useChartMargin( height, chartOptions, dataSorted, theme, horizontal );
- const [ svgWrapperRef, svgWrapperHeight ] = useElementHeight< HTMLDivElement >();
+ const [ svgWrapperRef, , svgWrapperHeight ] = useElementSize< HTMLDivElement >();
const chartRef = useRef< HTMLDivElement >( null );
// Check if children contain a Legend component (composition pattern)
diff --git a/projects/js-packages/charts/src/charts/bar-chart/test/bar-chart.test.tsx b/projects/js-packages/charts/src/charts/bar-chart/test/bar-chart.test.tsx
index 2c609270a9b..d3209c194f3 100644
--- a/projects/js-packages/charts/src/charts/bar-chart/test/bar-chart.test.tsx
+++ b/projects/js-packages/charts/src/charts/bar-chart/test/bar-chart.test.tsx
@@ -3,10 +3,10 @@ import userEvent from '@testing-library/user-event';
import { GlobalChartsProvider } from '../../../providers';
import BarChart from '../bar-chart';
-// Mock useElementHeight to return a non-zero height in jsdom so charts render
+// Mock useElementSize to return non-zero dimensions in jsdom so charts render
const mockRefCallback = jest.fn();
-jest.mock( '../../../hooks/use-element-height', () => ( {
- useElementHeight: () => [ mockRefCallback, 300 ], // Return test height to allow chart rendering
+jest.mock( '../../../hooks/use-element-size', () => ( {
+ useElementSize: () => [ mockRefCallback, 500, 300 ],
} ) );
describe( 'BarChart', () => {
diff --git a/projects/js-packages/charts/src/charts/line-chart/line-chart.tsx b/projects/js-packages/charts/src/charts/line-chart/line-chart.tsx
index 89336865dad..fc4d5fb7d75 100644
--- a/projects/js-packages/charts/src/charts/line-chart/line-chart.tsx
+++ b/projects/js-packages/charts/src/charts/line-chart/line-chart.tsx
@@ -14,7 +14,7 @@ import {
useXYChartTheme,
useChartDataTransform,
useChartMargin,
- useElementHeight,
+ useElementSize,
useHasLegendChild,
usePrefersReducedMotion,
} from '../../hooks';
@@ -288,7 +288,7 @@ const LineChartInternal = forwardRef< SingleChartRef, LineChartProps >(
const providerTheme = useGlobalChartsTheme();
const theme = useXYChartTheme( data );
const chartId = useChartId( providedChartId );
- const [ svgWrapperRef, svgWrapperHeight ] = useElementHeight< HTMLDivElement >();
+ const [ svgWrapperRef, , svgWrapperHeight ] = useElementSize< HTMLDivElement >();
const chartRef = useRef< HTMLDivElement >( null );
const [ selectedIndex, setSelectedIndex ] = useState< number | undefined >( undefined );
const [ isNavigating, setIsNavigating ] = useState( false );
diff --git a/projects/js-packages/charts/src/charts/line-chart/test/line-chart.test.tsx b/projects/js-packages/charts/src/charts/line-chart/test/line-chart.test.tsx
index f395f7c90c4..2da8f848971 100644
--- a/projects/js-packages/charts/src/charts/line-chart/test/line-chart.test.tsx
+++ b/projects/js-packages/charts/src/charts/line-chart/test/line-chart.test.tsx
@@ -8,10 +8,10 @@ import { GlobalChartsProvider, defaultTheme } from '../../../providers';
import LineChart, { LineChartUnresponsive } from '../line-chart';
import type { SingleChartRef } from '../../private/single-chart-context';
-// Mock useElementHeight to return a non-zero height in jsdom so charts render
+// Mock useElementSize to return non-zero dimensions in jsdom so charts render
const mockRefCallback = jest.fn();
-jest.mock( '../../../hooks/use-element-height', () => ( {
- useElementHeight: () => [ mockRefCallback, 300 ], // Return test height to allow chart rendering
+jest.mock( '../../../hooks/use-element-size', () => ( {
+ useElementSize: () => [ mockRefCallback, 500, 300 ],
} ) );
const customTheme = {
diff --git a/projects/js-packages/charts/src/charts/pie-chart/pie-chart.module.scss b/projects/js-packages/charts/src/charts/pie-chart/pie-chart.module.scss
index a53f79af2f7..ff3482db7ff 100644
--- a/projects/js-packages/charts/src/charts/pie-chart/pie-chart.module.scss
+++ b/projects/js-packages/charts/src/charts/pie-chart/pie-chart.module.scss
@@ -3,9 +3,20 @@
flex-direction: column;
overflow: hidden;
align-items: center;
- gap: 20px;
- &--legend-top {
- flex-direction: column-reverse;
+ // Fill parent when no explicit width/height provided
+ &--responsive {
+ height: 100%;
+ width: 100%;
+ }
+
+ &__svg-wrapper {
+ flex: 1;
+ min-height: 0; // Required for flex shrinking
+ min-width: 0; // Required for flex shrinking
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
}
diff --git a/projects/js-packages/charts/src/charts/pie-chart/pie-chart.tsx b/projects/js-packages/charts/src/charts/pie-chart/pie-chart.tsx
index 3e515f98a32..a005298d67b 100644
--- a/projects/js-packages/charts/src/charts/pie-chart/pie-chart.tsx
+++ b/projects/js-packages/charts/src/charts/pie-chart/pie-chart.tsx
@@ -2,11 +2,12 @@ import { Group } from '@visx/group';
import { Pie } from '@visx/shape';
import { useTooltip, useTooltipInPortal } from '@visx/tooltip';
import { __ } from '@wordpress/i18n';
+import { Stack } from '@wordpress/ui';
import clsx from 'clsx';
import { useCallback, useContext, useMemo } from 'react';
import { Legend, useChartLegendItems } from '../../components/legend';
import { BaseTooltip } from '../../components/tooltip';
-import { useElementHeight, useInteractiveLegendData, usePrefersReducedMotion } from '../../hooks';
+import { useElementSize, useInteractiveLegendData, usePrefersReducedMotion } from '../../hooks';
import {
GlobalChartsProvider,
useChartId,
@@ -25,6 +26,7 @@ import styles from './pie-chart.module.scss';
import type { LegendValueDisplay } from '../../components/legend';
import type { BaseChartProps, DataPointPercentage, Optional } from '../../types';
import type { ChartComponentWithComposition } from '../private/chart-composition';
+import type { GapSize } from '@wordpress/theme';
import type { SVGProps, MouseEvent, ReactNode, FC } from 'react';
/**
@@ -119,6 +121,13 @@ export interface PieChartProps extends BaseChartProps< DataPointPercentage[] > {
* When provided, replaces the default BaseTooltip with custom content.
*/
renderTooltip?: ( params: PieChartRenderTooltipParams ) => ReactNode;
+
+ /**
+ * Gap between chart elements (SVG, legend, children).
+ * Uses WordPress design system tokens.
+ * @default 'md'
+ */
+ gap?: GapSize;
}
// Base props type with optional responsive properties
@@ -175,6 +184,8 @@ const PieChartInternal = ( {
legendTextOverflow = 'wrap',
legendItemClassName,
legendShape = 'circle',
+ width: propWidth,
+ height: propHeight,
size,
animation,
thickness = 1,
@@ -188,10 +199,11 @@ const PieChartInternal = ( {
tooltipOffsetX = 0,
tooltipOffsetY = -15,
renderTooltip = renderDefaultPieTooltip,
+ gap = 'md',
}: PieChartProps ) => {
const providerTheme = useGlobalChartsTheme();
const chartId = useChartId( providedChartId );
- const [ legendRef, legendHeight ] = useElementHeight< HTMLDivElement >();
+ const [ svgWrapperRef, svgWrapperWidth, svgWrapperHeight ] = useElementSize< HTMLDivElement >();
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
useTooltip< DataPointPercentage >();
@@ -263,16 +275,24 @@ const PieChartInternal = ( {
);
}
- const width = size;
- const height = size;
- const adjustedHeight = showLegend && legendPosition === 'top' ? height - legendHeight : height;
+ // Calculate the actual pie size:
+ // - Measure available space from the svg-wrapper
+ // - If size prop provided: use it as max, but shrink if container is smaller
+ // - If no size prop: fill available space
+ const availableWidth = svgWrapperWidth > 0 ? svgWrapperWidth : 300;
+ const availableHeight = svgWrapperHeight > 0 ? svgWrapperHeight : 300;
+ const availableSize = Math.min( availableWidth, availableHeight );
+ const actualSize = size ? Math.min( size, availableSize ) : availableSize;
+
+ const width = actualSize;
+ const height = actualSize;
// Calculate radius based on width/height
- const radius = Math.min( width, adjustedHeight ) / 2;
+ const radius = Math.min( width, height ) / 2;
// Center the chart in the available space
const centerX = width / 2;
- const centerY = adjustedHeight / 2;
+ const centerY = height / 2;
// Calculate the angle between each (use original data length for consistent spacing)
const padAngle = gapScale * ( ( 2 * Math.PI ) / data.length );
@@ -300,166 +320,178 @@ const PieChartInternal = ( {
},
};
+ const legendElement = showLegend && (
+
+ );
+
return (
+
Click legend items to show/hide segments. The total value updates dynamically.
Additional information
-Additional information
+
+
Click legend items to show/hide segments. Percentages recalculate automatically for
visible segments.