diff --git a/projects/js-packages/charts/BREAKING_CHANGES.md b/projects/js-packages/charts/BREAKING_CHANGES.md new file mode 100644 index 0000000000000..a8d4ab45d04fe --- /dev/null +++ b/projects/js-packages/charts/BREAKING_CHANGES.md @@ -0,0 +1,93 @@ +# Breaking Changes - PieSemiCircleChart + +## Summary + +The `label` and `note` props have been removed from `PieSemiCircleChart` in favor of the more flexible composition API using the `children` prop. + +## Migration Guide + +### Before (Deprecated) +```tsx + +``` + +### After (New API) + +**Option 1: Direct Group usage** +```tsx +import { Group } from '@visx/group'; +import { Text } from '@visx/text'; + + + + + Operating Systems + + + Windows +10% + + + +``` + +**Option 2: Compound components (more explicit)** +```tsx +import { Group } from '@visx/group'; +import { Text } from '@visx/text'; + + + + + + Operating Systems + + + Windows +10% + + + + +``` + +## Affected Consumer Files + +### WooCommerce Analytics + +The following files in the WooCommerce Analytics repository need to be updated: + +1. **`/js/src/widgets/payments-by-method/payments-by-method.tsx`** (Lines 41-43) + - Uses: `label="$122K"` and `note="+3%"` + - Action: Convert to composition API + +2. **`/js/src/widgets/taxes-by-code/index.tsx`** (Lines 45-47) + - Uses: `label={label}` and `note={info}` + - Action: Convert to composition API + +3. **`/js/src/widgets/coupons-widget/coupons-widget.tsx`** (Line 82) + - Uses: `label=""` + - Action: Remove empty label prop + +4. **`/next-woocommerce-analytics/packages/widgets/src/shared/semi-circle-chart/semi-circle-chart.tsx`** (Line 93) + - Uses: `label=""` + - Action: Remove empty label prop + +### Jetpack Stats + +No breaking changes found - PieSemiCircleChart is not currently used in Jetpack Stats. + +## Notes + +- The `label=""` empty string usage can simply be removed without replacement +- For actual label/note values, use the composition API examples above +- Import `Group` from `@visx/group` and `Text` from `@visx/text` when using the composition API diff --git a/projects/js-packages/charts/changelog/charts-65-iterate-over-pie-and-piesemicircle-composition b/projects/js-packages/charts/changelog/charts-65-iterate-over-pie-and-piesemicircle-composition new file mode 100644 index 0000000000000..508251433ccfe --- /dev/null +++ b/projects/js-packages/charts/changelog/charts-65-iterate-over-pie-and-piesemicircle-composition @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Charts: improve pie semi circle chart composition diff --git a/projects/js-packages/charts/src/components/pie-semi-circle-chart/pie-semi-circle-chart.tsx b/projects/js-packages/charts/src/components/pie-semi-circle-chart/pie-semi-circle-chart.tsx index ba25fab031e1c..320a1693c6e31 100644 --- a/projects/js-packages/charts/src/components/pie-semi-circle-chart/pie-semi-circle-chart.tsx +++ b/projects/js-packages/charts/src/components/pie-semi-circle-chart/pie-semi-circle-chart.tsx @@ -1,7 +1,6 @@ import { localPoint } from '@visx/event'; import { Group } from '@visx/group'; import { Pie } from '@visx/shape'; -import { Text } from '@visx/text'; import { useTooltip, useTooltipInPortal } from '@visx/tooltip'; import clsx from 'clsx'; import { useCallback, useContext, useMemo } from 'react'; @@ -46,18 +45,42 @@ export interface PieSemiCircleChartProps extends BaseChartProps< DataPointPercen */ clockwise?: boolean; - /** - * Label text to display above the chart - */ - label?: string; - - /** - * Note text to display below the label - */ - note?: string; - /** * Use the children prop to render additional elements on the chart. + * Supports composition API with PieSemiCircleChart.SVG and PieSemiCircleChart.HTML components. + * + * @example + * // Render title and note using Group and Text (direct approach) + * + * + * + * Chart Title + * + * + * Subtitle or note + * + * + * + * + * @example + * // Using compound components for more explicit control + * + * + * + * + * Chart Title + * + * + * Subtitle or note + * + * + * + * + *
+ * Additional HTML content below the chart + *
+ *
+ *
*/ children?: ReactNode; @@ -133,8 +156,6 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( { legendItemClassName, legendShape = 'circle', legendValueDisplay = 'percentage', - label, - note, className, children, tooltipOffsetX = 0, @@ -316,26 +337,6 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( { } } - { /* Label and note text */ } - - - { label } - - - { note } - - - { /* Render SVG children from composition API */ } { svgChildren } diff --git a/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.api.mdx b/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.api.mdx index b0b435f338566..aaa0178b621ca 100644 --- a/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.api.mdx +++ b/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.api.mdx @@ -16,16 +16,67 @@ Main component for rendering semi-circular pie charts. | `width` | `number` | `400` | Width of the chart in pixels (height is automatically half of width) | | `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 | -| `note` | `string` | - | Additional text displayed below the label | +| `children` | `ReactNode` | - | Custom content to render. Supports Group elements or PieSemiCircleChart.SVG/HTML compound components | | `withTooltips` | `boolean` | `false` | Enable interactive tooltips on hover | | `showLegend` | `boolean` | `false` | Display legend below the chart | | `legendOrientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Legend layout direction | | `legendAlignment` | `'start' \| 'center' \| 'end'` | `'center'` | Legend alignment within its position | | `legendPosition` | `'top' \| 'bottom'` | `'bottom'` | Legend position (where the legend appears) | | `legendShape` | `'circle' \| 'rect' \| 'line'` | `'circle'` | Shape of legend indicators | +| `legendValueDisplay` | `'percentage' \| 'value' \| 'valueDisplay' \| 'none'` | `'percentage'` | Type of value to display in legend | +| `legendMaxWidth` | `number` | - | Maximum width for legend items | +| `legendTextOverflow` | `'wrap' \| 'truncate'` | `'wrap'` | How to handle long legend text | +| `legendItemClassName` | `string` | - | Custom CSS class for legend items | | `className` | `string` | - | Additional CSS class for the container | | `chartId` | `string` | - | Optional unique identifier (auto-generated if not provided) | +| `tooltipOffsetX` | `number` | `0` | Horizontal offset for tooltip positioning in pixels | +| `tooltipOffsetY` | `number` | `-15` | Vertical offset for tooltip positioning in pixels | + +## Compound Components + +### PieSemiCircleChart.SVG + +Wrapper for SVG elements to be rendered inside the chart (positioned within the semi-circle). + +**Usage:** +```jsx + + + + Title + + + +``` + +### PieSemiCircleChart.HTML + +Wrapper for HTML elements to be rendered outside the SVG (below the chart). + +**Usage:** +```jsx + + +
Additional HTML content
+
+
+``` + +### PieSemiCircleChart.Legend + +Legend component for flexible placement within the composition API. + +**Props:** +- All legend-related props from the main component (orientation, alignment, position, shape, etc.) + +**Usage:** +```jsx + + + + + +``` ## DataPointPercentage Type diff --git a/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.docs.mdx b/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.docs.mdx index 2721b8bfe1db1..5dcbf01d26bfa 100644 --- a/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.docs.mdx +++ b/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.docs.mdx @@ -42,11 +42,18 @@ const data = [ data={ data } width={ 600 } thickness={ 0.4 } - label="Operating Systems" - note="Windows +10%" withTooltips={ true } showLegend={ true } -/>` } +> + + + Operating Systems + + + Windows +10% + + +
` } /> The chart automatically validates data to ensure positive values and meaningful percentages, displaying error states for invalid data configurations. @@ -106,9 +113,16 @@ Add `withTooltips` to enable hover interactions that display detailed informatio data={ data } width={ 600 } withTooltips={ true } - label="OS Distribution" - note="Hover segments for details" -/>` } +> + + + OS Distribution + + + Hover segments for details + + +` } /> ### Counter-Clockwise Direction @@ -121,8 +135,13 @@ Use the `clockwise` prop to control the rendering direction of segments: data={ data } width={ 600 } clockwise={ false } - label="Counter-clockwise Rendering" -/>` } +> + + + Counter-clockwise Rendering + + +` } /> ### Different Thickness Values @@ -136,16 +155,26 @@ Adjust the visual weight of the chart using the `thickness` prop: data={ data } width={ 400 } thickness={ 0.2 } - label="Thin Ring" -/> +> + + + Thin Ring + + + // Thick ring (thickness: 0.8) ` } +> + + + Thick Ring + + +` } /> ## Responsive Behavior @@ -162,9 +191,14 @@ The chart can be made responsive by omitting the `width` prop, allowing it to ad ` } +> + + + Responsive Chart + + +` } /> The responsive behavior maintains the 2:1 aspect ratio (width:height) and scales proportionally. @@ -195,25 +229,36 @@ Segments automatically use theme colors, but you can override individual segment ` } +> + + + Custom Colors + + +` } /> -### Label and Note Styling - -The chart includes built-in styling for labels and notes with appropriate typography hierarchy: +### Custom Text Styling -- **Label**: 16px, font-weight 600, positioned above the chart -- **Note**: 14px, positioned below the label +You can add custom titles and notes using the composition API with full control over styling: ` } +> + + {/* Primary heading - 16px, font-weight 600 */} + + Primary Heading + + {/* Secondary note - 14px */} + + Secondary information or context + + +` } /> ### Theme Integration @@ -293,8 +338,13 @@ The chart gracefully handles single data points, rendering a complete semi-circl ` } +> + + + Single Category + + +` } /> ## Advanced Features @@ -386,3 +436,50 @@ const partialData = [ { label: 'Progress', value: 60, percentage: 60 }, // Shows 60% of semi-circle ];` } /> + +### Migrating from label/note Props (Breaking Change) + +The `label` and `note` props have been removed in favor of the more flexible composition API. Here's how to migrate: + + + +// ✅ New API - Direct Group usage + + + + Operating Systems + + + Windows +10% + + + + +// ✅ New API - Compound components (more explicit) + + + + + Operating Systems + + + Windows +10% + + + +` } +/> diff --git a/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.stories.tsx index 408077883698f..1589cc7637c67 100644 --- a/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.stories.tsx @@ -53,9 +53,23 @@ export const Default: Story = { resize: 'none', thickness: 0.4, data, - label: 'OS', - note: 'Windows +10%', clockwise: true, + children: ( + + + OS + + + Windows +10% + + + ), }, }; @@ -95,24 +109,46 @@ export const WithCompositionLegend: Story = { + > + + + Performance Metrics + + + Q4 2023 Results + + +

Composition API with Legend Component

- + + + + Performance Metrics + + + Q4 2023 Results + + + + OS + + + Windows +10% + + + ), }, parameters: { docs: { @@ -261,15 +311,15 @@ export const CompositionAPI: Story = { >

With Custom SVG Elements

- + + + OS Usage + + + Q4 2023 +

With Custom Legend and HTML Content

- + + + + + Performance + + + Latest Results + + +
-

Legacy Support - Direct Group Components

+

Direct Group Components

- For backward compatibility, Group components are still supported directly: + Group components are supported directly for simpler use cases:

- + + + Direct Usage + + + Simple and clean! + - Direct Group usage + Additional annotation diff --git a/projects/js-packages/charts/src/components/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx b/projects/js-packages/charts/src/components/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx index 38098ed471bcc..d71fd681a3384 100644 --- a/projects/js-packages/charts/src/components/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +++ b/projects/js-packages/charts/src/components/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx @@ -1,5 +1,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { Group } from '@visx/group'; +import { Text } from '@visx/text'; import { act } from 'react'; import { GlobalChartsProvider } from '../../../providers'; import PieSemiCircleChart from '../pie-semi-circle-chart'; @@ -35,13 +37,23 @@ describe( 'PieSemiCircleChart', () => { expect( segments ).toHaveLength( 2 ); } ); - it( 'renders label and note when provided', () => { - const label = 'Chart Title'; - const note = 'Additional Info'; - renderPieChart( { data: mockData, label, note } ); + it( 'renders custom children content', () => { + renderPieChart( { + data: mockData, + children: ( + + + Chart Title + + + Additional Info + + + ), + } ); - expect( screen.getByText( label ) ).toBeInTheDocument(); - expect( screen.getByText( note ) ).toBeInTheDocument(); + expect( screen.getByText( 'Chart Title' ) ).toBeInTheDocument(); + expect( screen.getByText( 'Additional Info' ) ).toBeInTheDocument(); } ); it( 'shows legend when showLegend is true', () => { @@ -178,4 +190,83 @@ describe( 'PieSemiCircleChart', () => { ).toBeInTheDocument(); } ); } ); + + describe( 'Composition API', () => { + it( 'renders children with SVG content', () => { + renderPieChart( { + data: mockData, + children: ( + + + Custom Title + + + ), + } ); + + expect( screen.getByText( 'Custom Title' ) ).toBeInTheDocument(); + } ); + + it( 'renders PieSemiCircleChart.SVG compound component', () => { + renderPieChart( { + data: mockData, + children: ( + + + + SVG Component Title + + + + ), + } ); + + expect( screen.getByText( 'SVG Component Title' ) ).toBeInTheDocument(); + } ); + + it( 'renders PieSemiCircleChart.HTML compound component', () => { + renderPieChart( { + data: mockData, + children: ( + +
HTML Content
+
+ ), + } ); + + expect( screen.getByTestId( 'html-content' ) ).toBeInTheDocument(); + expect( screen.getByText( 'HTML Content' ) ).toBeInTheDocument(); + } ); + + it( 'renders mixed SVG and HTML compound components', () => { + renderPieChart( { + data: mockData, + children: [ + + + + SVG Title + + + , + +
Chart Footer
+
, + ], + } ); + + expect( screen.getByText( 'SVG Title' ) ).toBeInTheDocument(); + expect( screen.getByTestId( 'footer' ) ).toBeInTheDocument(); + } ); + + it( 'renders Legend as compound component', () => { + renderPieChart( { + data: mockData, + children: , + } ); + + expect( screen.getByText( 'Category A' ) ).toBeInTheDocument(); + expect( screen.getByText( 'Category B' ) ).toBeInTheDocument(); + } ); + } ); } ); diff --git a/projects/js-packages/charts/src/providers/chart-context/stories/index.stories.tsx b/projects/js-packages/charts/src/providers/chart-context/stories/index.stories.tsx index fe1506ab589bc..5e0c3516f15e2 100644 --- a/projects/js-packages/charts/src/providers/chart-context/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/providers/chart-context/stories/index.stories.tsx @@ -1,4 +1,6 @@ import { Meta, StoryObj } from '@storybook/react'; +import { Group } from '@visx/group'; +import { Text } from '@visx/text'; import { LineChart, BarChart, @@ -186,10 +188,21 @@ const ChartGrid = ( { args }: { args: StoryArgs } ) => { + > + + + Semi-Circle Chart + + + @@ -247,10 +260,21 @@ const ChartGridWithColorOverrides = ( { args }: { args: StoryArgs } ) => { + > + + + Semi-Circle Chart + + +