diff --git a/projects/js-packages/charts/changelog/CHARTS-183-legend-stories-and-documentation b/projects/js-packages/charts/changelog/CHARTS-183-legend-stories-and-documentation new file mode 100644 index 000000000000..5667330bdf98 --- /dev/null +++ b/projects/js-packages/charts/changelog/CHARTS-183-legend-stories-and-documentation @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Standardize legend stories and documentation across all chart types. diff --git a/projects/js-packages/charts/src/charts/bar-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/charts/bar-chart/stories/index.stories.tsx index a7cd75c1e30c..b6044c96dda4 100644 --- a/projects/js-packages/charts/src/charts/bar-chart/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/charts/bar-chart/stories/index.stories.tsx @@ -238,18 +238,16 @@ SmartFormatting.parameters = { }, }; -export const WithInteractiveLegend: Story = { +export const WithLegend: Story = { args: { ...Default.args, showLegend: true, - legendInteractive: true, - chartId: 'bar-chart-with-interactive-legend', }, parameters: { docs: { description: { story: - 'Bar chart with interactive legend. Click on legend items to toggle series visibility. When all series are hidden, a message will be displayed prompting you to click legend items to show data again.', + 'Props-based legend using `showLegend` and the `legend` config object. Use Storybook controls to adjust legend position, alignment, orientation, shape, and interactivity.', }, }, }, @@ -260,41 +258,24 @@ export const WithCompositionLegend: StoryObj< typeof BarChart > = { render: args => { const legend = extractLegendConfig( args ); return ( - + ); }, - parameters: { - docs: { - description: { - story: - 'Demonstrates using the composition API with `` as a child component. This provides the same functionality as the `showLegend` prop but allows for more flexible composition patterns.', - }, - }, - }, -}; - -// Story showcasing legend customization controls -export const CustomLegendPositioning: Story = { args: { - withTooltips: true, - data: medalCountsData.slice( 0, 3 ), // Use first 3 series for cleaner legend - gridVisibility: 'x', - maxWidth: 1200, - resizeDebounceTime: 300, - containerHeight: '400px', - // showLegend defaults to false, explicitly enabling for demonstration - showLegend: true, - legendOrientation: 'vertical', - legendAlignment: 'start', - legendPosition: 'top', + ...Default.args, }, parameters: { docs: { description: { story: - 'Bar chart with top-left positioned vertical legend. This demonstrates non-default legend positioning to showcase different legend placement possibilities.', + 'Composition API using `` as a child component for explicit legend placement and configuration. This is the recommended approach for flexible legend positioning.', }, }, }, diff --git a/projects/js-packages/charts/src/charts/leaderboard-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/charts/leaderboard-chart/stories/index.stories.tsx index dc4737414276..667c0c4f5497 100644 --- a/projects/js-packages/charts/src/charts/leaderboard-chart/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/charts/leaderboard-chart/stories/index.stories.tsx @@ -384,17 +384,12 @@ export const WithLegend: Story = { loading: false, showLegend: true, }, -}; - -export const CustomLegendLabels: Story = { - args: { - data: sampleData, - withComparison: true, - loading: false, - showLegend: true, - legendLabels: { - primary: 'Aug 11-Sep 9, 2025', - comparison: 'Jul 11-Aug 11, 2025', + parameters: { + docs: { + description: { + story: + 'Props-based legend using `showLegend` and the `legend` config object. Use Storybook controls to adjust legend position, alignment, orientation, shape, and interactivity.', + }, }, }, }; @@ -403,7 +398,11 @@ export const WithCompositionLegend: Story = { render: args => { const legend = extractLegendConfig( args ); return ( - + ` as a child component for explicit legend placement and configuration. This is the recommended approach for flexible legend positioning.', }, }, }, diff --git a/projects/js-packages/charts/src/charts/line-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/charts/line-chart/stories/index.stories.tsx index ab4b07a05934..1503e39982f0 100644 --- a/projects/js-packages/charts/src/charts/line-chart/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/charts/line-chart/stories/index.stories.tsx @@ -141,38 +141,17 @@ Animation.args = { animation: true, }; -export const WithInteractiveLegend: StoryObj< typeof LineChart > = Template.bind( {} ); -WithInteractiveLegend.args = { +export const WithLegend: StoryObj< typeof LineChart > = Template.bind( {} ); +WithLegend.args = { ...lineChartStoryArgs, - chartId: 'interactive-legend-demo', showLegend: true, - legendInteractive: true, }; -WithInteractiveLegend.parameters = { +WithLegend.parameters = { docs: { description: { story: - 'Line chart with interactive legend. Click or tap legend items to toggle series visibility. Use Tab to focus legend items, then Enter or Space to toggle. Series colors remain stable when toggling visibility.', - }, - }, -}; - -export const CustomLegendPositioning: StoryObj< typeof LineChart > = Template.bind( {} ); -CustomLegendPositioning.args = { - ...lineChartStoryArgs, - showLegend: true, - legendAlignment: 'start', - legendPosition: 'top', - legendOrientation: 'horizontal', - withLegendGlyph: true, -}; - -CustomLegendPositioning.parameters = { - docs: { - description: { - story: - 'Line chart with top-left positioned horizontal legend. This demonstrates non-default legend positioning to showcase different legend placement possibilities with temperature data for London, Canberra, and Mars.', + 'Props-based legend using `showLegend` and the `legend` config object. Use Storybook controls to adjust legend position, alignment, orientation, shape, and interactivity.', }, }, }; @@ -182,15 +161,24 @@ export const WithCompositionLegend: StoryObj< typeof LineChart > = { render: args => { const legend = extractLegendConfig( args ); return ( - + ); }, + args: { + ...Default.args, + }, parameters: { docs: { description: { - story: 'Legend used with LineChart using the composition API, positioned below the chart.', + story: + 'Composition API using `` as a child component for explicit legend placement and configuration. This is the recommended approach for flexible legend positioning.', }, }, }, diff --git a/projects/js-packages/charts/src/charts/pie-chart/stories/donut.stories.tsx b/projects/js-packages/charts/src/charts/pie-chart/stories/donut.stories.tsx index 7babb98bcd80..cb06a2e223de 100644 --- a/projects/js-packages/charts/src/charts/pie-chart/stories/donut.stories.tsx +++ b/projects/js-packages/charts/src/charts/pie-chart/stories/donut.stories.tsx @@ -5,7 +5,6 @@ import { } from '@wordpress/components'; import { Fragment } from 'react'; import { BaseLegendItem } from '../../../components/legend/types'; -import { GlobalChartsProvider } from '../../../providers'; import { chartDecorator, sharedChartArgTypes, @@ -217,50 +216,26 @@ export const WithLegend: Story = { showLegend: true, containerHeight: '500px', }, -}; - -export const WithCompositionLegend: Story = { - render: args => { - const legend = extractLegendConfig( args ); - return ( - - - - User Stats - - - 100K Total - - - - - ); - }, - args: { - data, - thickness: 0.5, - }, parameters: { docs: { description: { story: - 'Demonstrates the donut chart composition API, allowing flexible combination of chart elements and legends.', + 'Props-based legend using `showLegend` and the `legend` config object. Use Storybook controls to adjust legend position, alignment, orientation, shape, and interactivity.', }, }, }, }; -export const InteractiveLegend: Story = { - render: args => ( - - { + const legend = extractLegendConfig( args ); + return ( + @@ -270,68 +245,19 @@ export const InteractiveLegend: Story = { 100K Total -

- Click legend items to show/hide segments. The total value updates dynamically. -

-
-
- ), + + + ); + }, args: { data, thickness: 0.5, - legendInteractive: true, }, parameters: { docs: { description: { story: - 'Interactive donut chart with clickable legend. Segments can be hidden/shown, and percentages recalculate automatically. Requires chartId and GlobalChartsProvider.', - }, - }, - }, -}; - -export const CustomLegendPositioning: Story = { - args: { - ...Default.args, - thickness: 0.4, - showLegend: true, - legendOrientation: 'vertical', - legendAlignment: 'start', - legendPosition: 'top', - containerHeight: '450px', - data: [ - { - label: 'Desktop', - value: 45000, - valueDisplay: '45K', - percentage: 45, - }, - { - label: 'Mobile', - value: 35000, - valueDisplay: '35K', - percentage: 35, - }, - { - label: 'Tablet', - value: 20000, - valueDisplay: '20K', - percentage: 20, - }, - ], - children: ( - - - Distribution - - - ), - }, - parameters: { - docs: { - description: { - story: 'Donut chart with vertical legend positioned at the top left.', + 'Composition API using `` as a child component for explicit legend placement and configuration. This is the recommended approach for flexible legend positioning.', }, }, }, diff --git a/projects/js-packages/charts/src/charts/pie-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/charts/pie-chart/stories/index.stories.tsx index e7e2b349a8b3..f8abca4d2fa7 100644 --- a/projects/js-packages/charts/src/charts/pie-chart/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/charts/pie-chart/stories/index.stories.tsx @@ -180,104 +180,37 @@ export const WithLegend: Story = { ...Default.args, showLegend: true, }, -}; - -export const WithCompositionLegend: Story = { - render: args => { - const legend = extractLegendConfig( args ); - return ( - - - - ); - }, - args: { - data, - }, parameters: { docs: { description: { story: - 'Demonstrates the new composition API allowing flexible component composition. The chart can be used with traditional props or with explicit child components for more control.', + 'Props-based legend using `showLegend` and the `legend` config object. Use Storybook controls to adjust legend position, alignment, orientation, shape, and interactivity.', }, }, }, }; -export const InteractiveLegend: Story = { - render: args => ( - - { + const legend = extractLegendConfig( args ); + return ( + -

- Click legend items to show/hide segments. Percentages recalculate automatically for - visible segments. -

-
-
- ), - args: { - data, - size: 400, - legendInteractive: true, - }, - parameters: { - docs: { - description: { - story: - 'Interactive legends allow users to toggle segment visibility by clicking legend items. When segments are hidden, the visible segments are recalculated to total 100%. Requires chartId and GlobalChartsProvider.', - }, - }, + + + ); }, -}; - -export const CustomLegendPositioning: Story = { args: { - data: [ - { - label: 'Desktop', - value: 45000, - valueDisplay: '45K', - percentage: 45, - }, - { - label: 'Mobile', - value: 35000, - valueDisplay: '35K', - percentage: 35, - }, - { - label: 'Tablet', - value: 20000, - valueDisplay: '20K', - percentage: 20, - }, - ], - thickness: 1, // Full pie chart - gapScale: 0.03, - padding: 20, - cornerScale: 0.03, - withTooltips: true, - showLegend: true, - legendOrientation: 'vertical', - legendAlignment: 'center', - legendPosition: 'top', - legendShape: 'circle', - size: 400, - containerWidth: '432px', - containerHeight: '432px', + data, }, parameters: { docs: { description: { story: - 'Pie chart with top-end positioned vertical legend. This demonstrates non-default legend positioning to showcase different legend placement possibilities with device usage data.', + 'Composition API using `` as a child component for explicit legend placement and configuration. This is the recommended approach for flexible legend positioning.', }, }, }, 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 b65fa39b869c..280470d9cd25 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 @@ -1,6 +1,5 @@ import { Group } from '@visx/group'; import { Text } from '@visx/text'; -import { GlobalChartsProvider } from '../../../providers'; import { chartDecorator, sharedChartArgTypes, @@ -141,99 +140,38 @@ export const WithLegend: Story = { ...Default.args, showLegend: true, }, -}; - -export const WithCompositionLegend: Story = { - render: args => { - const legend = extractLegendConfig( args ); - return ( - - - - ); - }, - args: { - data, - }, parameters: { docs: { description: { story: - 'Demonstrates the semi-circle chart composition API, allowing flexible component composition with explicit legend placement.', + 'Props-based legend using `showLegend` and the `legend` config object. Use Storybook controls to adjust legend position, alignment, orientation, shape, and interactivity.', }, }, }, }; -export const InteractiveLegend: Story = { - render: args => ( - +export const WithCompositionLegend: Story = { + render: args => { + const legend = extractLegendConfig( args ); + return ( -

- Click legend items to show/hide segments. Percentages adjust automatically. -

+
-
- ), - args: { - data, - legendInteractive: true, - }, - parameters: { - docs: { - description: { - story: - 'Interactive semi-circle chart with clickable legend items. Hidden segments are excluded and percentages recalculate. Requires chartId and GlobalChartsProvider.', - }, - }, + ); }, -}; - -export const CustomLegendPositioning: Story = { args: { - thickness: 0.4, - 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, - }, - ], - label: 'OS', - note: 'Windows +10%', - withTooltips: true, - showLegend: true, - legendOrientation: 'vertical', - legendAlignment: 'end', - legendPosition: 'top', - legendShape: 'circle', + data, }, parameters: { docs: { description: { story: - 'Semi-circle pie chart with right-top positioned vertical legend. This demonstrates non-default legend positioning to showcase different legend placement possibilities with OS usage data.', + 'Composition API using `` as a child component for explicit legend placement and configuration. This is the recommended approach for flexible legend positioning.', }, }, }, diff --git a/projects/js-packages/charts/src/components/legend/stories/index.docs.mdx b/projects/js-packages/charts/src/components/legend/stories/index.docs.mdx index ebf60b4e8e51..de9b83ac4341 100644 --- a/projects/js-packages/charts/src/components/legend/stories/index.docs.mdx +++ b/projects/js-packages/charts/src/components/legend/stories/index.docs.mdx @@ -17,6 +17,12 @@ The Legend component offers multiple ways to display chart legends, from simple language="tsx" code={ `import { Legend, LineChart } from '@automattic/charts'; + // Composition API (recommended) + + + + + // Standalone usage with manual data - - // Automatic data from chart context - - - ` + />` } /> ## API Reference @@ -93,52 +93,44 @@ For detailed information about all optional props, see the [Legend API Reference ## Integration with Charts -### Using with LineChart +### Composition API (Recommended) -Legends can be positioned independently from the chart, retrieving data automatically via hooks: +The recommended way to add legends to charts is the composition API. Use the chart's `Legend` compound component as a child for explicit placement and configuration: - ` } -/> + code={ `import { LineChart } from '@automattic/charts'; -### Using with BarChart + + + ` } +/> -Position legends vertically beside charts for a side-by-side layout: +This works with all chart types that support legends: - - - ` } + + + ` } /> +#### Key Benefits + +- **Explicit Placement**: The legend is part of the chart's component tree, making placement clear and predictable +- **Automatic Data**: Legend items are derived from the chart's data automatically +- **Full Configuration**: Supports all legend props including `position`, `alignment`, `orientation`, `shape`, and `interactive` +- **Type Safety**: Full TypeScript support with chart-specific legend types + ### Standalone Legend with Chart Context -The Legend component's most powerful feature is its ability to automatically retrieve data from charts using the chart context: +For decoupled layouts such as dashboards, the Legend component can retrieve data from charts automatically using the chart context: @@ -157,7 +149,6 @@ The Legend component's most powerful feature is its ability to automatically ret {/* Standalone legend that automatically gets data from chart context */} ` } @@ -169,13 +160,6 @@ The Legend component's most powerful feature is its ability to automatically ret 2. **Data Retrieval**: The Legend component can then retrieve this data using the same `chartId` 3. **Decoupled Display**: The legend can be placed anywhere in your layout, completely independent from the chart -#### Key Benefits - -- **Flexible Layouts**: Create complex dashboard layouts with centralized legend areas -- **Consistent Legends**: Multiple charts can share legend styles and positioning -- **Dynamic Updates**: Legend automatically updates when chart data changes -- **No Prop Drilling**: No need to pass legend data through multiple component levels - #### Important Notes - The chart and legend must be wrapped in the same GlobalChartsProvider context @@ -189,7 +173,7 @@ Interactive legends allow users to toggle series visibility by clicking or using ### Basic Interactive Legend -Enable interactive legends by setting `legend.interactive` on the chart. When `showLegend` is enabled, the chart's built-in legend will automatically become interactive: +Enable interactive legends using the composition API by passing `interactive` to the chart's Legend compound component: @@ -197,8 +181,18 @@ Enable interactive legends by setting `legend.interactive` on the chart. When `s language="tsx" code={ `import { LineChart } from '@automattic/charts'; - + + ` } +/> + +Alternatively, use the props-based approach with `showLegend` and the `legend` config object: + +` } @@ -220,7 +214,6 @@ Interactive legends also work with standalone Legend components. Set `legend.int ` } @@ -277,7 +270,6 @@ The Legend component provides comprehensive text overflow handling for long labe language="tsx" code={ `` } @@ -293,7 +285,6 @@ By default, legends use rectangular shapes, but you can customize the glyph shap language="tsx" code={ `` } /> diff --git a/projects/js-packages/charts/src/components/legend/stories/index.stories.tsx b/projects/js-packages/charts/src/components/legend/stories/index.stories.tsx index 72c4fa00b44f..8a18f61d9fb9 100644 --- a/projects/js-packages/charts/src/components/legend/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/components/legend/stories/index.stories.tsx @@ -8,7 +8,6 @@ import { themeArgTypes, sharedThemeArgs, } from '../../../stories'; -import { useChartLegendItems } from '../hooks/use-chart-legend-items'; import { Legend } from '../legend'; import type { SeriesData, DataPointPercentage } from '../../../types'; @@ -106,45 +105,29 @@ export const Vertical: Story = { }, }; -// Story showing use with LineChart data -const WithLineChartData = () => { - const legendItems = useChartLegendItems( lineChartData, { - showValues: false, - } ); - - return ( -
- - -
- ); -}; - +// Story showing composition API with LineChart export const WithLineChart: Story = { - render: () => , -}; - -// Story showing use with BarChart data -const WithBarChartData = () => { - const legendItems = useChartLegendItems( barChartData ); - - return ( -
- - -
- ); + render: () => ( + + + + ), }; +// Story showing composition API with BarChart export const WithBarChart: Story = { - render: () => , + render: () => ( + + + + ), }; // Story showing standalone legend using chartId to automatically get data from context @@ -162,7 +145,7 @@ const StandaloneLegendWithChartIdComponent = () => { withLegendGlyph={ false } /> { /* Standalone legend that automatically gets data from chart context */ } - + ); }; @@ -317,7 +300,6 @@ export const AlignmentOptions: Story = { { label: 'Series 2', value: '35%', color: '#80C8FF' }, { label: 'Series 3', value: '40%', color: '#44B556' }, ], - orientation: 'horizontal', alignment: 'start', }, }; @@ -393,7 +375,6 @@ export const CustomShape: Story = { { label: 'Desktop', value: '65%', color: '#3858E9' }, { label: 'Mobile', value: '35%', color: '#80C8FF' }, ], - orientation: 'horizontal', shape: 'circle', }, };