diff --git a/projects/js-packages/charts/changelog/charts-169-fix-chart-height-and-size-calculation-for-pie-semi-circle-chart b/projects/js-packages/charts/changelog/charts-169-fix-chart-height-and-size-calculation-for-pie-semi-circle-chart
new file mode 100644
index 000000000000..9ca16111a8d0
--- /dev/null
+++ b/projects/js-packages/charts/changelog/charts-169-fix-chart-height-and-size-calculation-for-pie-semi-circle-chart
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Charts: fix PieSemiCircleChart height and size calculations to be responsive by default, maintaining 2:1 width-to-height ratio.
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 80ba480c0b1c..75802258c0f6 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
@@ -32,7 +32,6 @@ import { useBarChartOptions } from './private';
import type { BaseChartProps, DataPointDate, SeriesData, Optional } from '../../types';
import type { ResponsiveConfig } from '../private/with-responsive';
import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
-import type { GapSize } from '@wordpress/theme';
import type { FC, ReactNode, ComponentType } from 'react';
export interface BarChartProps extends BaseChartProps< SeriesData[] > {
@@ -42,12 +41,6 @@ export interface BarChartProps extends BaseChartProps< SeriesData[] > {
showZeroValues?: boolean;
legendInteractive?: boolean;
children?: ReactNode;
- /**
- * Gap between chart elements (SVG, legend, children).
- * Uses WordPress design system tokens.
- * @default 'md'
- */
- gap?: GapSize;
}
// Base props type with optional responsive properties
diff --git a/projects/js-packages/charts/src/charts/bar-chart/stories/index.api.mdx b/projects/js-packages/charts/src/charts/bar-chart/stories/index.api.mdx
index e2c797aefc57..556eb3479f51 100644
--- a/projects/js-packages/charts/src/charts/bar-chart/stories/index.api.mdx
+++ b/projects/js-packages/charts/src/charts/bar-chart/stories/index.api.mdx
@@ -32,6 +32,7 @@ Main chart component with responsive behavior by default.
| `options` | `ChartOptions` | `{}` | Advanced axis and scale configuration |
| `className` | `string` | - | Additional CSS class name |
| `children` | `ReactNode` | - | Child components (e.g., ``) |
+| `gap` | `GapSize` | `'md'` | Gap between chart elements (SVG, legend, children). Uses WordPress design system tokens |
## BarChart.Legend
diff --git a/projects/js-packages/charts/src/charts/line-chart/stories/index.api.mdx b/projects/js-packages/charts/src/charts/line-chart/stories/index.api.mdx
index 0ee42ccab3c2..add6e12bf21e 100644
--- a/projects/js-packages/charts/src/charts/line-chart/stories/index.api.mdx
+++ b/projects/js-packages/charts/src/charts/line-chart/stories/index.api.mdx
@@ -38,6 +38,7 @@ Main chart component with responsive behavior by default.
| `onPointerMove` | `(event: EventHandlerParams) => void?` | - | Pointer move event handler |
| `onPointerOut` | `(event: PointerEvent) => void?` | - | Pointer out event handler |
| `children` | `ReactNode?` | - | Child components (e.g., annotations) |
+| `gap` | `GapSize` | `'md'` | Gap between chart elements (SVG, legend, children). Uses WordPress design system tokens |
## LineChart.AnnotationsOverlay
diff --git a/projects/js-packages/charts/src/charts/line-chart/types.ts b/projects/js-packages/charts/src/charts/line-chart/types.ts
index 6ca0d12ca1b3..0d5d5629bce2 100644
--- a/projects/js-packages/charts/src/charts/line-chart/types.ts
+++ b/projects/js-packages/charts/src/charts/line-chart/types.ts
@@ -7,7 +7,6 @@ import type {
} from '../../types';
import type { GlyphProps } from '@visx/xychart';
import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
-import type { GapSize } from '@wordpress/theme';
import type { ReactNode, SVGProps, FC } from 'react';
export type LineChartAnnotationProps = {
@@ -44,12 +43,6 @@ export interface LineChartProps extends BaseChartProps< SeriesData[] > {
};
legendInteractive?: boolean;
children?: ReactNode;
- /**
- * Gap between chart elements (SVG, legend, children).
- * Uses WordPress design system tokens.
- * @default 'md'
- */
- gap?: GapSize;
}
export type TooltipDatum = {
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 ccca4e30e199..6282a8496786 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
@@ -26,7 +26,6 @@ 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';
/**
@@ -121,13 +120,6 @@ 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
diff --git a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss
index 47d9df637406..de49033a51ec 100644
--- a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss
+++ b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss
@@ -1,21 +1,27 @@
.pie-semi-circle-chart {
- display: flex;
- flex-direction: column;
- text-align: center;
- gap: 20px;
+ // Fill parent when no explicit width/height provided
+ &--responsive {
+ height: 100%;
+ width: 100%;
+ }
- &--legend-top {
- flex-direction: column-reverse;
+ // Flex wrapper that fills remaining Stack space and measures the SVG area
+ &__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;
}
.label {
- margin-bottom: 0; // Add space between label and pie chart
- font-weight: 600; // Make label more prominent than note
- font-size: 16px; // Set explicit font size
+ font-weight: 600;
+ font-size: 16px;
}
.note {
- margin-top: 0; // Add space between pie chart and note
- font-size: 14px; // Slightly smaller text for hierarchy
+ font-size: 14px;
}
}
diff --git a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx
index 46c9d62764a2..a262cdf03906 100644
--- a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx
+++ b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx
@@ -3,6 +3,7 @@ import { Pie } from '@visx/shape';
import { Text } from '@visx/text';
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';
@@ -52,10 +53,13 @@ const renderDefaultPieSemiCircleTooltip = ( {
};
const PAD_ANGLE = 0.03; // Padding between segments
+const DEFAULT_WIDTH = 400;
export interface PieSemiCircleChartProps extends BaseChartProps< DataPointPercentage[] > {
/**
- * Width of the chart in pixels; height would be half of this value calculated automatically.
+ * Explicit width of the chart container in pixels.
+ * When omitted, the chart fills its parent container's width.
+ * The chart always maintains a 2:1 width-to-height ratio, constrained by available space.
*/
width?: number;
@@ -157,7 +161,8 @@ const validateData = ( data: DataPointPercentage[] ) => {
const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
data,
chartId: providedChartId,
- width = 400,
+ width: propWidth,
+ height: propHeight,
thickness = 0.4,
clockwise = true,
withTooltips = false,
@@ -179,9 +184,11 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
tooltipOffsetX = 0,
tooltipOffsetY = -15,
renderTooltip = renderDefaultPieSemiCircleTooltip,
+ gap = 'md',
} ) => {
const chartId = useChartId( providedChartId );
- const [ legendRef, , legendHeight ] = useElementSize< HTMLDivElement >();
+ // Measure the SVG wrapper to calculate constrained dimensions
+ const [ svgWrapperRef, svgWrapperWidth, svgWrapperHeight ] = useElementSize< HTMLDivElement >();
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
useTooltip< DataPointPercentage >();
@@ -295,10 +302,17 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
const prefersReducedMotion = usePrefersReducedMotion();
+ const effectiveWidth = propWidth || DEFAULT_WIDTH;
+
if ( ! isValid ) {
+ const errorWidth = propHeight
+ ? Math.min( propWidth || propHeight * 2, propHeight * 2 )
+ : effectiveWidth;
+ const errorHeight = errorWidth / 2;
+
return (
-
+
);
};
diff --git a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.api.mdx b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.api.mdx
index d8ab0a8e0373..26082d51b976 100644
--- a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.api.mdx
+++ b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.api.mdx
@@ -13,7 +13,8 @@ Main component for rendering semi-circular pie charts.
| Prop | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| `data` | `DataPointPercentage[]` | - | **Required.** Array of data points to display |
-| `width` | `number` | `400` | Width of the chart in pixels (height is automatically half of width) |
+| `width` | `number` | responsive | Width constraint in pixels. By default the chart fills its parent container. When provided, constrains the chart to this width while maintaining the 2:1 aspect ratio |
+| `height` | `number` | responsive | Height constraint in pixels. By default the chart fills its parent container. When provided, the chart will reduce its width if needed so that height (= width / 2) does not exceed this value |
| `thickness` | `number` | `0.4` | Thickness of the pie segments (0-1, where 1 is full thickness) |
| `clockwise` | `boolean` | `true` | Direction of segment rendering |
| `label` | `string` | - | Text displayed above the chart |
@@ -28,6 +29,7 @@ Main component for rendering semi-circular pie charts.
| `legendPosition` | `'top' \| 'bottom'` | `'bottom'` | Legend position (where the legend appears) |
| `legendShape` | `'circle' \| 'rect' \| 'line'` | `'circle'` | Shape of legend indicators |
| `className` | `string` | - | Additional CSS class for the container |
+| `gap` | `GapSize` | `'md'` | Gap between chart elements (SVG, legend, children). Uses WordPress design system tokens |
| `chartId` | `string` | - | Optional unique identifier (auto-generated if not provided) |
## PieSemiCircleChartRenderTooltipParams Type
diff --git a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.docs.mdx b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.docs.mdx
index 50c5d90d2ad1..a6811fe5f363 100644
--- a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.docs.mdx
+++ b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.docs.mdx
@@ -17,36 +17,33 @@ The PieSemiCircleChart component renders data as segments in a semi-circular arc
language="jsx"
code={ `import { PieSemiCircleChart } from '@automattic/charts';
-const data = [
- {
- label: 'MacOS',
- value: 30000,
- valueDisplay: '30K',
- percentage: 30,
- },
- {
- label: 'Linux',
- value: 22000,
- valueDisplay: '22K',
- percentage: 22,
- },
- {
- label: 'Windows',
- value: 48000,
- valueDisplay: '48K',
- percentage: 48,
- },
-];
-
-` }
+ const data = [
+ {
+ label: 'MacOS',
+ value: 30000,
+ valueDisplay: '30K',
+ percentage: 5,
+ },
+ {
+ label: 'Linux',
+ value: 22000,
+ valueDisplay: '22K',
+ percentage: 1,
+ },
+ {
+ label: 'Windows',
+ value: 80000,
+ valueDisplay: '80K',
+ percentage: 2,
+ },
+ ];
+
+ ` }
/>
The chart automatically validates data to ensure positive values and meaningful percentages, displaying error states for invalid data configurations.
@@ -65,23 +62,12 @@ The simplest implementation requires only data with proper percentage values. Un
` }
+ code={ `` }
/>
### Required Props
@@ -103,12 +89,11 @@ Add `withTooltips` to enable hover interactions that display detailed informatio
` }
+ data={ data }
+ withTooltips={ true }
+ label="OS"
+ note="Windows +10%"
+ />` }
/>
### Counter-Clockwise Direction
@@ -118,11 +103,11 @@ Use the `clockwise` prop to control the rendering direction of segments:
` }
+ data={ data }
+ width={ 600 }
+ clockwise={ false }
+ label="Counter-clockwise Rendering"
+ />` }
/>
### Different Thickness Values
@@ -132,42 +117,44 @@ Adjust the visual weight of the chart using the `thickness` prop:
+
-// Thick ring (thickness: 0.8)
-` }
+ // Thick ring (thickness: 0.8)
+ ` }
/>
## Responsive Behavior
-### Responsive Width
-
-The chart can be made responsive by omitting the `width` prop, allowing it to adapt to its container:
+By default, charts **fill their parent container's dimensions** while maintaining a 2:1 width-to-height aspect ratio. The parent must have an explicit height:
-
+
` }
+ code={ `// Fill parent container (default) - parent needs explicit height
+
+
+ // Fixed dimensions - chart constrains to 2:1 ratio within these bounds
+ ` }
/>
-The responsive behavior maintains the 2:1 aspect ratio (width:height) and scales proportionally.
+The chart always maintains a 2:1 width-to-height ratio. When both `width` and `height` are provided, the chart constrains to whichever dimension is more restrictive. Use `width` and/or `height` to constrain the chart to specific pixel dimensions.
+
+
+
+For more details on responsive behavior, see the [Responsive Design section](./?path=/docs/js-packages-charts-library-introduction--docs#responsive-design) in the introduction.
## Styling and Customization
@@ -178,25 +165,25 @@ Segments automatically use theme colors, but you can override individual segment
` }
+ {
+ label: 'Primary',
+ value: 60,
+ percentage: 60,
+ color: '#3366CC', // Custom blue
+ },
+ {
+ label: 'Secondary',
+ value: 40,
+ percentage: 40,
+ color: '#DC3912', // Custom red
+ },
+ ];
+
+ ` }
/>
### Label and Note Styling
@@ -209,11 +196,11 @@ The chart includes built-in styling for labels and notes with appropriate typogr
` }
+ data={ data }
+ width={ 600 }
+ label="Primary Heading"
+ note="Secondary information or context"
+ />` }
/>
## Theming Integration
@@ -243,9 +230,9 @@ The Pie Semi Circle Chart component supports an optional entry animation that cr
language="jsx"
code={ `` }
/>
@@ -267,20 +254,20 @@ Control legend placement using alignment properties:
+
-// Vertical legend on the right
-` }
+ // Vertical legend on the right
+ ` }
/>
### Legend Shape Options
@@ -321,18 +308,18 @@ The chart gracefully handles single data points, rendering a complete semi-circl
` }
+ {
+ label: 'Complete',
+ value: 100,
+ percentage: 100,
+ },
+ ];
+
+ ` }
/>
## Advanced Features
@@ -344,25 +331,25 @@ Use the `valueDisplay` property to show formatted values in tooltips and legends
` }
+ {
+ label: 'Users',
+ value: 15000,
+ valueDisplay: '15K users', // Custom formatted display
+ percentage: 60,
+ },
+ {
+ label: 'Revenue',
+ value: 10000,
+ valueDisplay: '$10K', // Currency formatting
+ percentage: 40,
+ },
+ ];
+
+ ` }
/>
### Chart Integration
@@ -382,16 +369,16 @@ Semi-circle charts use the same data format as full pie charts, making migration
+
-// Semi-circle chart (uses width instead of size)
-` }
+ // Semi-circle chart (uses width instead of size)
+ ` }
/>
### Key Differences from Full Pie Charts
@@ -408,19 +395,19 @@ Unlike full pie charts that require percentages to sum to exactly 100:
diff --git a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.stories.tsx
index 165fb0a3250f..c69847edee9c 100644
--- a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.stories.tsx
+++ b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/stories/index.stories.tsx
@@ -10,7 +10,7 @@ import {
partialOsUsageData as data,
themeArgTypes,
} from '../../../stories';
-import { PieSemiCircleChart, PieSemiCircleChartUnresponsive } from '../index';
+import { PieSemiCircleChart } from '../index';
import type { Meta, StoryObj } from '@storybook/react';
type StoryArgs = ChartStoryArgs< React.ComponentProps< typeof PieSemiCircleChart > >;
@@ -34,6 +34,14 @@ const meta: Meta< StoryArgs > = {
step: 10,
},
},
+ height: {
+ control: {
+ type: 'range',
+ min: 100,
+ max: 1000,
+ step: 10,
+ },
+ },
thickness: {
control: {
type: 'range',
@@ -51,14 +59,48 @@ type Story = StoryObj< StoryArgs >;
export const Default: Story = {
args: {
...sharedThemeArgs,
- containerWidth: '600px',
- resize: 'none',
thickness: 0.4,
data,
label: 'OS',
note: 'Windows +10%',
clockwise: true,
},
+ parameters: {
+ docs: {
+ description: {
+ story:
+ 'Responsive semi-circle pie chart. Resize the dashed container to see the chart adapt while maintaining a 2:1 width-to-height ratio.',
+ },
+ },
+ },
+};
+
+export const FixedDimensions: Story = {
+ render: args => (
+
+ ),
+ args: {
+ ...Default.args,
+ resize: 'none',
+ width: 600,
+ height: 300,
+ },
+ parameters: {
+ docs: {
+ description: {
+ story:
+ 'Semi-circle pie chart with fixed pixel dimensions. The chart will maintain a 2:1 width-to-height ratio within the provided dimensions.',
+ },
+ },
+ },
};
export const Animation: Story = {
@@ -91,51 +133,18 @@ export const WithLegend: Story = {
export const WithCompositionLegend: Story = {
render: args => (
-
-
-
Traditional Props-based Legend
-
-
-
-
Composition API with Legend Component
-
-
-
-
-
+
+
+
),
args: {
data,
- containerWidth: '900px',
},
argTypes: {
legendInteractive: {
@@ -155,29 +164,25 @@ export const WithCompositionLegend: Story = {
export const InteractiveLegend: Story = {
render: args => (
-
-
Interactive Semi-Circle Chart
+
Click legend items to show/hide segments. Percentages adjust automatically.
-
-
+
),
args: {
data,
- width: 400,
},
parameters: {
docs: {
@@ -191,9 +196,6 @@ export const InteractiveLegend: Story = {
export const CustomLegendPositioning: Story = {
args: {
- containerWidth: '600px',
- containerHeight: '350px',
- resize: 'none',
thickness: 0.4,
data: [
{
@@ -234,20 +236,6 @@ export const CustomLegendPositioning: Story = {
},
};
-const responsiveArgs = { ...Default.args, resize: 'both' as const };
-delete responsiveArgs.width;
-export const Responsiveness: Story = {
- args: responsiveArgs,
- parameters: {
- docs: {
- description: {
- story:
- 'Semi-circle pie chart with responsive behavior. Uses width prop for unified width/height handling.',
- },
- },
- },
-};
-
export const ErrorStates: Story = {
render: () => (
@@ -281,14 +269,14 @@ export const ErrorStates: Story = {
),
args: {
- containerHeight: '600px',
+ containerHeight: 600,
},
parameters: {
docs: {
@@ -317,7 +305,7 @@ export const CompositionAPI: Story = {
With Custom SVG Elements
With Custom Legend and HTML Content
= args => = Template.bind( {} );
diff --git a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx
index 7f7cc8ef9f04..eb8519f5db54 100644
--- a/projects/js-packages/charts/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx
+++ b/projects/js-packages/charts/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx
@@ -3,6 +3,15 @@ import userEvent from '@testing-library/user-event';
import { GlobalChartsProvider } from '../../../providers';
import PieSemiCircleChart from '../pie-semi-circle-chart';
+// Mock useParentSize so the responsive wrapper returns predictable dimensions in tests
+jest.mock( '@visx/responsive', () => ( {
+ useParentSize: jest.fn( () => ( {
+ parentRef: { current: null },
+ width: 400,
+ height: 200,
+ } ) ),
+} ) );
+
// Mock data for testing
const mockData = [
{
@@ -167,15 +176,15 @@ describe( 'PieSemiCircleChart', () => {
expect( thinPathD ).not.toBe( thickPathD );
} );
- it( 'renders with correct dimensions', () => {
- const width = 400;
- render( );
+ it( 'renders with correct dimensions from measured container', () => {
+ // Mock returns width:400, height:200 — chart should render at 400×200 (2:1 ratio)
+ render( );
const svg = screen.getByTestId( 'pie-chart-svg' );
- expect( svg ).toHaveAttribute( 'width', width.toString() );
- expect( svg ).toHaveAttribute( 'height', ( width / 2 ).toString() );
- expect( svg ).toHaveAttribute( 'viewBox', `0 0 ${ width } ${ width / 2 }` );
+ expect( svg ).toHaveAttribute( 'width', '400' );
+ expect( svg ).toHaveAttribute( 'height', '200' );
+ expect( svg ).toHaveAttribute( 'viewBox', '0 0 400 200' );
} );
describe( 'Data Validation', () => {
@@ -216,6 +225,37 @@ describe( 'PieSemiCircleChart', () => {
} );
} );
+ describe( 'Responsive wrapper', () => {
+ it( 'fills parent container (height:100%) by default', () => {
+ render( );
+ const wrapper = screen.getByTestId( 'responsive-wrapper' );
+ expect( wrapper ).toHaveStyle( { height: '100%' } );
+ } );
+
+ it( 'constrains chart to 2:1 ratio from measured dimensions', () => {
+ // Mock returns width:400, height:200, so chart renders at 400×200 (2:1 ratio)
+ render( );
+ const svg = screen.getByTestId( 'pie-chart-svg' );
+ expect( svg ).toHaveAttribute( 'width', '400' );
+ expect( svg ).toHaveAttribute( 'height', '200' );
+ } );
+
+ it( 'constrains chart width when container height is shorter than 2:1 ratio', () => {
+ // If parent height is 100px, chart should be at most 200×100 (not 400×200)
+ const { useParentSize } = jest.requireMock( '@visx/responsive' );
+ useParentSize.mockReturnValueOnce( {
+ parentRef: { current: null },
+ width: 400,
+ height: 100,
+ } );
+ render( );
+ const svg = screen.getByTestId( 'pie-chart-svg' );
+ // chartWidth = min(400, 100*2) = 200, chartHeight = 100
+ expect( svg ).toHaveAttribute( 'width', '200' );
+ expect( svg ).toHaveAttribute( 'height', '100' );
+ } );
+ } );
+
describe( 'Interactive Legend', () => {
test( 'filters segments when interactive legend is enabled and segment is toggled', async () => {
const user = userEvent.setup();
diff --git a/projects/js-packages/charts/src/types.ts b/projects/js-packages/charts/src/types.ts
index 781f064b1341..e59c352c1c44 100644
--- a/projects/js-packages/charts/src/types.ts
+++ b/projects/js-packages/charts/src/types.ts
@@ -7,6 +7,7 @@ import type { LegendShape } from '@visx/legend/lib/types';
import type { ScaleInput, ScaleType } from '@visx/scale';
import type { TextProps } from '@visx/text/lib/Text';
import type { EventHandlerParams, GlyphProps, GridStyles, LineStyles } from '@visx/xychart';
+import type { GapSize } from '@wordpress/theme';
import type { CSSProperties, PointerEvent, ReactNode } from 'react';
import type { GoogleDataTableColumn, GoogleDataTableRow } from 'react-google-charts';
@@ -459,6 +460,13 @@ export type BaseChartProps< T = DataPoint | DataPointDate | LeaderboardEntry > =
*/
animation?: boolean;
+ /**
+ * Gap between chart elements (SVG, legend, children).
+ * Uses WordPress design system tokens.
+ * @default 'md'
+ */
+ gap?: GapSize;
+
/**
* More options for the chart.
*/