Skip to content

Charts: Add standalone Legend component with Chart Context system #44245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 41 commits into
base: trunk
Choose a base branch
from

Conversation

annacmc
Copy link
Contributor

@annacmc annacmc commented Jul 9, 2025

Fixes #50

Proposed changes:

  • Adds standalone Legend component that can be used independently from charts or shared between multiple charts
  • Adds useChartLegendData hook for generating legend data from chart data sources (SeriesData, DataPointPercentage)
  • Adds Chart Context system allowing components to register and share legend data across the component tree
  • Updates all chart components (LineChart, BarChart, PieChart, PieSemiCircleChart) to use the new legend system
  • Maintains backward compatibility - all existing chart APIs work unchanged
  • Adds comprehensive Storybook documentation with examples of standalone usage and chart integration

Key Features:

  • Standalone usage: <Legend items={[...]} /> for custom legend items
  • Context-based usage: <Legend chartId="my-chart" /> to display legend for a specific chart
  • Flexible data sources: Supports both time series data and percentage data
  • Multiple chart support: Single legend can display data from multiple charts
  • Responsive design: Works with existing responsive chart system

Other information:

  • Have you written new tests for your changes, if applicable? (Tests split to follow-up PR for easier review)
  • Have you checked the E2E test CI results, and verified that your changes do not break them?
  • Have you tested your changes on WordPress.com, if applicable (if so, you'll see a generated comment below with a script to run)?

Jetpack product discussion

Does this pull request change what data or activity we track or use?

No data tracking changes.

Testing instructions:

1. Test Standalone Legend Component

import { Legend } from '@automattic/charts';

const legendItems = [
  { label: 'Series 1', color: '#ff0000', value: '42%' },
  { label: 'Series 2', color: '#00ff00', value: '58%' }
];

<Legend items={legendItems} orientation="horizontal" />

@annacmc annacmc self-assigned this Jul 9, 2025
@annacmc annacmc requested a review from Copilot July 9, 2025 13:36
Copy link
Contributor

github-actions bot commented Jul 9, 2025

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the add/charts-50-standalone-legend-component branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack add/charts-50-standalone-legend-component

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

Copy link
Contributor

github-actions bot commented Jul 9, 2025

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • 🔴 Add a "[Type]" label (Bug, Enhancement, Janitorial, Task).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!

@github-actions github-actions bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Jul 9, 2025
Copilot

This comment was marked as outdated.

Copy link

jp-launch-control bot commented Jul 9, 2025

Code Coverage Summary

Coverage changed in 3 files.

File Coverage Δ% Δ Uncovered
projects/js-packages/charts/src/components/bar-chart/bar-chart.tsx 69/75 (92.00%) -1.15% 1 ❤️‍🩹
projects/js-packages/charts/src/components/line-chart/line-chart.tsx 78/84 (92.86%) -1.05% 1 ❤️‍🩹
projects/js-packages/charts/src/components/pie-chart/pie-chart.tsx 48/52 (92.31%) -1.69% 1 ❤️‍🩹

2 files are newly checked for coverage.

File Coverage
projects/js-packages/charts/src/components/legend/use-chart-legend-data.ts 22/27 (81.48%) 💚
projects/js-packages/charts/src/components/legend/legend.tsx 7/8 (87.50%) 💚

Full summary · PHP report · JS report

Coverage check overridden by Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR .

@annacmc annacmc force-pushed the add/charts-50-standalone-legend-component branch 2 times, most recently from ce9977f to b018ace Compare July 11, 2025 10:07
@annacmc annacmc requested a review from Copilot July 14, 2025 23:51
Copilot

This comment was marked as outdated.

@annacmc annacmc requested a review from Copilot July 15, 2025 08:57
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds a standalone Legend component and refactors existing chart components to use a common BaseLegend and a new useChartLegendData hook for generating legend items, with optional context integration.

  • Introduces useChartLegendData hook and BaseLegend component, plus a Legend wrapper for context-aware rendering.
  • Refactors BarChart, LineChart, PieChart, and PieSemiCircleChart to use the new legend hook and BaseLegend.
  • Updates ChartContext to export and trigger re-renders on chart registration changes.

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
projects/js-packages/charts/src/types.ts Changed import to import type for AnnotationStyles
projects/js-packages/charts/src/providers/chart-context/chart-context.tsx Exported ChartContext and added state version to trigger updates
projects/js-packages/charts/src/index.ts Exposed Legend, BaseLegend, and useChartLegendData exports
projects/js-packages/charts/src/components/shared/with-responsive.tsx Added overflow: 'hidden' to responsive wrapper style
projects/js-packages/charts/src/components/pie-semi-circle-chart/pie-semi-circle-chart.tsx Swapped Legend for BaseLegend and updated legend comment
projects/js-packages/charts/src/components/pie-chart/pie-chart.tsx Refactored legend logic, added context-aware provider detection
projects/js-packages/charts/src/components/pie-chart/pie-chart.module.scss Added overflow: hidden to .pie-chart class
projects/js-packages/charts/src/components/line-chart/line-chart.tsx Refactored legend logic, hook usage, and context provider wrapping
projects/js-packages/charts/src/components/legend/use-chart-legend-data.ts Added new hook to generate legend items from chart data
projects/js-packages/charts/src/components/legend/types.ts Introduced BaseLegendProps and updated LegendProps
projects/js-packages/charts/src/components/legend/stories/index.stories.tsx Updated stories to demonstrate standalone and context-aware legends
projects/js-packages/charts/src/components/legend/legend.tsx New Legend wrapper component that reads from context or props
projects/js-packages/charts/src/components/legend/legend.test.tsx Added tests for Legend context and fallback behaviors
projects/js-packages/charts/src/components/legend/index.ts Centralized exports of Legend, BaseLegend, hook, and types
projects/js-packages/charts/src/components/legend/base-legend.tsx Updated forwardRef generic to BaseLegendProps
projects/js-packages/charts/src/components/bar-chart/bar-chart.tsx Switched to new legend hook/BaseLegend and context wrapping
projects/js-packages/charts/changelog/add-charts-50-standalone-legend-component Added changelog entry for standalone legend feature
Comments suppressed due to low confidence (1)

projects/js-packages/charts/src/components/legend/use-chart-legend-data.ts:139

  • The new useChartLegendData hook lacks unit tests. Add focused tests to verify item generation for series data, point data, and options combinations.
export function useChartLegendData<

theme: ChartTheme,
options: ChartLegendOptions = {}
): LegendItemWithGlyph[] | LegendItemWithoutGlyph[] {
const { showValues = true, withGlyph = false, glyphSize = 8, renderGlyph } = options;
Copy link
Preview

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defaulting showValues to true will cause series-based legends (e.g., BarChart, LineChart) to display the count of data points as the value. Consider defaulting showValues to false or requiring explicit enablement to preserve previous behavior.

Suggested change
const { showValues = true, withGlyph = false, glyphSize = 8, renderGlyph } = options;
const { showValues = false, withGlyph = false, glyphSize = 8, renderGlyph } = options;

Copilot uses AI. Check for mistakes.

@@ -30,7 +33,8 @@ export const ChartProvider: FC< ChartProviderProps > = ( { children } ) => {
unregisterChart,
getChartData,
} ),
[ registerChart, unregisterChart, getChartData ]
// eslint-disable-next-line react-hooks/exhaustive-deps
Copy link
Preview

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lint suppression for react-hooks/exhaustive-deps can be removed by including all actual dependencies (e.g., registerChart, unregisterChart, getChartData, and version) in the dependency array of useMemo.

Suggested change
// eslint-disable-next-line react-hooks/exhaustive-deps

Copilot uses AI. Check for mistakes.

annacmc and others added 14 commits July 16, 2025 09:23
- Create ChartLegendOptions interface with showValues, withGlyph, glyphSize, and renderGlyph options
- Add ChartLegendProps interface extending LegendProps with chartId for future context integration
- Implement useChartLegendData hook with overloads for SeriesData[] and DataPoint[]/DataPointPercentage[]
- Support both series data and point data with proper color, shape, and glyph handling
- Include valueDisplay preference for DataPointPercentage items
- Return properly typed LegendItemWithGlyph[] or LegendItemWithoutGlyph[] arrays
- Create ChartLegend component as a wrapper around BaseLegend
- Accept chartId prop for future context integration
- Pass through all legend props to BaseLegend for consistent behavior
- Enables standalone legend usage independent of chart showLegend prop
- Replace inline legend items creation with useChartLegendData hook
- Pass withGlyph and glyphSize options to maintain existing functionality
- Eliminates duplicate legend creation logic
- Maintains all existing legend behavior and styling
- Replace inline legend items creation with useChartLegendData hook
- Add useChartTheme hook to get provider theme for consistent legend colors
- Move hook call to proper position to avoid rules-of-hooks violation
- Remove duplicate legend creation logic
- Maintains all existing legend behavior and styling
- Replace inline legend items creation with useChartLegendData hook in both components
- Enable showValues option for both charts to display percentages in legend
- Move hook calls to proper position to avoid rules-of-hooks violations
- Remove duplicate legend creation logic
- Maintain valueDisplay support for properly formatted percentage values
- Keep all existing legend behavior and styling
- Export ChartLegend component and useChartLegendData hook from main index
- Export ChartLegendProps and ChartLegendOptions types
- Add chart-legend module index file with all exports
- Makes standalone legend functionality available to consumers
- Create multiple story examples showing different usage patterns
- Include stories for standalone usage, chart integration, and alignment options
- Add stories demonstrating usage with LineChart, BarChart, and PieChart
- Include examples for multiple charts sharing a legend
- Add comprehensive test suite for ChartLegend component and useChartLegendData hook
- Test both SeriesData and DataPointPercentage data types
- Test memoization, glyph handling, and value display functionality
- Fix TypeScript any usage in mock functions
- Resolve merge conflicts from chart context foundation PR
- Fix duplicate legendItems declarations in PieChart and PieSemiCircleChart
- Maintain custom color handling in PieSemiCircleChart using accessors
- Fix type-only import for AnnotationStyles
- Fix duplicate identifier in ChartLegend stories
- Add chart context integration to automatically retrieve legend data
- Support chartId prop for standalone usage
- Maintain fallback to items prop for manual data passing
- Add story demonstrating standalone usage with chartId
- Component works with showLegend=false charts
- Accepts all BaseLegend props through inheritance
- Create validateSeriesData for line/bar charts
- Create validatePercentageData for pie charts
- Create validateSemiCircleData for semi-circle charts
- Create validateBarChartData for bar-specific validation
- Eliminates ~60 lines of duplicated validation code
- Enhances existing Legend to support chartId prop
- Automatically retrieves legend data from chart context
- Falls back to manual items prop for backward compatibility
- Enables standalone legend usage with <Legend chartId="my-chart" />
- Extract formatPointValue for consistent value formatting
- Extract createBaseLegendItem for common legend item creation
- Extract processSeriesData and processPointData functions
- Eliminates ~50 lines of duplicated mapping logic
- Improves code readability and testability
- Split LegendProps into BaseLegendProps and LegendProps
- BaseLegendProps for direct usage (items required)
- LegendProps for context usage (items optional, chartId added)
- Export new Legend component alongside BaseLegend
- Export useChartLegendData and ChartLegendOptions
annacmc and others added 23 commits July 16, 2025 09:24
- Export ChartContext to enable direct useContext usage
- Allows Legend component to handle missing provider gracefully
- Enables flexible context usage patterns
- LineChart: Use validateSeriesData instead of custom validation
- BarChart: Use validateBarChartData for label requirements
- PieChart: Use validatePercentageData with 100% requirement
- PieSemiCircleChart: Use validateSemiCircleData for >0% requirement
- Update imports to use BaseLegend for internal chart usage
- Eliminates ~60 lines of duplicated validation code
- Eliminates technical debt from duplicate component
- Consolidates legend functionality into enhanced Legend component
- Maintains clean architecture with single legend implementation
- Export enhanced Legend and BaseLegend components
- Export useChartLegendData from legend module
- Export LegendProps, BaseLegendProps, ChartLegendOptions types
- Remove ChartLegend references in favor of unified Legend
- Replace basic legend stories with enhanced documentation
- Add context integration examples with chartId usage
- Add real-world dashboard layout example
- Document standalone legend capabilities
- Include code examples and usage patterns
- Demonstrate both manual and automatic legend data
- Add missing newline at end of file
- Fix JSDoc formatting consistency
- Update chart components to import from correct legend paths
- Change Legend to BaseLegend for internal chart usage
- Fix import order to comply with ESLint rules

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Change ChartProvider from useRef to useState for chart storage
- Ensures Legend component re-renders when chart data is registered
- Fixes standalone legend not displaying in Storybook
- Use version counter approach with useRef for chart storage
- Increment version when charts are registered/unregistered
- Prevents infinite loops while maintaining reactivity
Adds tests for the Legend wrapper component covering:
- Direct items prop usage
- Context-based data retrieval via chartId
- Fallback behavior when context data is unavailable
- Null rendering for edge cases
- Prop forwarding to BaseLegend
- Context data prioritization

All 8 new tests pass, bringing total legend test coverage to 17 tests.
The Legend component was not re-rendering when chart data changed in the context because it wasn't properly subscribing to context changes. Added useMemo with context as a dependency to ensure the component re-renders when the chart context updates.

This fixes the issue where standalone Legend components would not display data from charts registered in the same ChartProvider context.
The LineChart component was always creating its own ChartProvider, which prevented standalone Legend components from accessing chart data when both were wrapped in the same ChartProvider.

Modified LineChart to only create a new ChartProvider when it's not already inside an existing one, allowing it to share context with other components.
Similar to LineChart, the PieChart component was always creating its own ChartProvider, which prevented standalone Legend components from accessing chart data when both were wrapped in the same ChartProvider.

Modified PieChart to only create a new ChartProvider when it's not already inside an existing one, allowing it to share context with other components.
Improved legend readability by adding a space between the label text and value (e.g., "Desktop 65%" instead of "Desktop65%"). This makes the legend items more visually clear and easier to read.
Similar to LineChart and PieChart, the BarChart component was always creating its own ChartProvider, which prevented standalone Legend components from accessing chart data when both were wrapped in the same ChartProvider.

Modified BarChart to only create a new ChartProvider when it's not already inside an existing one, allowing it to share context with other components. This fixes the "Sales by Quarter" chart legend items not showing in the dashboard story.
Added overflow: hidden to both the PieChart CSS and the withResponsive HOC container to prevent chart content from flowing out of its designated container bounds. This fixes the issue where pie charts in constrained layouts (like the dashboard story) would visually overflow their containers.
Changed the default value of showValues from true to false in useChartLegendData hook to preserve the previous behavior where SeriesData legends don't show data counts by default. This prevents unintended display of data point counts in legends for LineChart and BarChart components.

Addresses Copilot feedback about preserving existing functionality.
The version dependency is needed to trigger re-renders when charts are registered/unregistered, but ESLint can't detect this relationship. Added a comment explaining why the lint suppression is necessary.

The useMemo needs to recreate the context value when the version changes (via registerChart/unregisterChart calls) to ensure components using the context re-render with updated chart data.
Added focused unit tests for the useChartLegendData hook to verify legend item generation for different data types and configurations. Tests cover:

- SeriesData handling (with and without values)
- DataPointPercentage handling (with and without values)
- Glyph handling (with and without glyphs)
- Edge cases (empty, null, undefined data)
- Memoization behavior
- Theme color cycling
- Default option values

Addresses Copilot feedback about adding focused tests for the hook.
1. Fix Legend ref prop error in bar-chart.tsx:
   - Wrapped Legend component in div with ref since Legend doesn't support ref prop
   - This maintains legend height measurement functionality

2. Remove textColor from ChartTheme mock in test file:
   - textColor property doesn't exist in ChartTheme type
   - Removed from mock data to fix type error
  gridColorDark properties to the ChartTheme mock in the
   test file
@annacmc annacmc force-pushed the add/charts-50-standalone-legend-component branch from b76937a to 2b9e9e9 Compare July 16, 2025 00:53
annacmc added 3 commits July 16, 2025 10:57
- Remove use-chart-legend-data.test.tsx (271 lines)
- Revert legend.test.tsx to original 9 tests (from 17 tests)
- Tests will be added in follow-up branch add/charts-50-tests

This makes the main PR focus on core functionality for easier review.
@annacmc annacmc added the Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR label Jul 16, 2025
- Remove unused TooltipContext and useEffect imports from line-chart.tsx
- Remove unused useState import and version state from chart-context.tsx
- Completes ESLint cleanup after removing dead code
@annacmc annacmc changed the title Add/charts 50 standalone legend component Charts: Add standalone Legend component with Chart Context system Jul 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR [JS Package] Charts RNA [Status] In Progress [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant