Skip to content

Conversation

annacmc
Copy link
Contributor

@annacmc annacmc commented Oct 15, 2025

Proposed changes:

  • Add toggleSeriesVisibility, isSeriesVisible, and getHiddenSeries methods to GlobalChartsContext for managing series visibility state
  • Implement visibility state management in GlobalChartsProvider using a Map to track hidden series per chart
  • Add interactive prop to Legend component to enable clickable legend items (defaults to false for backward compatibility)
  • Add click and keyboard event handlers to BaseLegend with full accessibility support (ARIA attributes, focus management, Enter/Space key navigation)
  • Add CSS styles for interactive legend states including hover effects, focus outlines, and visual feedback for hidden series (reduced opacity, strikethrough)
  • Filter visible series in LineChart, BarChart, and PieChart based on visibility state from context
  • Preserve original series indices when filtering to maintain consistent color assignments across visibility changes
  • Add legendInteractive argType to Storybook configuration
  • Add InteractiveLegend story demonstrating the feature with a working example

Other information:

  • Have you written new tests for your changes, if applicable?
  • 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)?

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

No, it does not.

Testing instructions:

In Storybook:

  • Navigate to JS Packages -> Charts -> Composites -> Legend -> InteractiveLegend story
  • Observe the line chart displaying two series: Desktop and Mobile
  • Click on the "Desktop" legend item
    • Desktop line should disappear from the chart
    • Desktop legend item should show reduced opacity (40%) and strikethrough text
    • Desktop legend item should have aria-label indicating "hidden" state
  • Click on the "Desktop" legend item again
    • Desktop line should reappear with its original color (not Mobile's color)
    • Desktop legend item should return to normal appearance
  • Click on the "Mobile" legend item
    • Mobile line should disappear
    • Mobile should maintain its original color when toggled back on
  • Test keyboard navigation:
    • Tab to focus on legend items
    • Press Enter or Space to toggle visibility
    • Verify focus outline appears on focused items
  • Test that non-interactive legends (other stories) continue to work as before
  • Verify all chart types work with interactive legends by testing with BarChart and PieChart

Backward Compatibility:

  • Verify that existing charts without the interactive prop continue to work exactly as before
  • Confirm that legends without chartId or GlobalChartsProvider work as standalone legends
  • Test that all 498 existing tests pass without modification

@annacmc annacmc added [Type] Enhancement Changes to an existing feature — removing, adding, or changing parts of it [Status] Needs Review This PR is ready for review. labels Oct 15, 2025
@annacmc annacmc self-assigned this Oct 15, 2025
Copy link
Contributor

github-actions bot commented Oct 15, 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-54-legends-toggling-visibility-of-series branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack add/charts-54-legends-toggling-visibility-of-series

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

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!

@annacmc annacmc requested a review from Copilot October 15, 2025 12:20
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 interactive legend support to toggle series visibility across charts. Key changes introduce visibility state management in the global charts context, wire up interactive legend behaviors with full keyboard/ARIA support, and update chart components to respect hidden series while preserving color consistency via original series indices.

  • Add series visibility API to GlobalChartsContext (toggle/isVisible/getHiddenSeries) and implement state via Map<chartId, Set>
  • Update LineChart, BarChart, and PieChart to filter hidden series and preserve original indices for consistent colors
  • Make Legend items interactive (click and keyboard), with new styles and Storybook story; pass chartId automatically from context

Reviewed Changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
projects/js-packages/charts/src/stories/legend-config.tsx Adds a Storybook argType for enabling interactive legends
projects/js-packages/charts/src/providers/chart-context/types.ts Extends GlobalChartsContextValue with methods for series visibility
projects/js-packages/charts/src/providers/chart-context/global-charts-provider.tsx Implements visibility state and exposes toggle/isVisible/getHiddenSeries
projects/js-packages/charts/src/components/pie-chart/pie-chart.tsx Filters hidden series and preserves original indices for colors
projects/js-packages/charts/src/components/line-chart/line-chart.tsx Filters hidden series; uses original indices to keep colors/IDs stable
projects/js-packages/charts/src/components/legend/types.ts Adds interactive and chartId props to BaseLegend/Legend props
projects/js-packages/charts/src/components/legend/stories/index.stories.tsx Adds InteractiveLegend story demonstrating toggling
projects/js-packages/charts/src/components/legend/private/base-legend.tsx Adds interactive behaviors, handlers, ARIA, and styling hooks
projects/js-packages/charts/src/components/legend/private/base-legend.module.scss Adds styles for interactive and inactive legend states
projects/js-packages/charts/src/components/legend/legend.tsx Passes chartId from context to BaseLegend
projects/js-packages/charts/src/components/bar-chart/bar-chart.tsx Filters hidden series; maintains original index for color/pattern consistency
projects/js-packages/charts/changelog/add-charts-54-legends-toggling-visibility-of-series Adds changelog entry

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 61 to 66
legendInteractive: {
control: { type: 'boolean' as const },
table: { category: 'Legend' },
description:
'Enable interactive legend items that can toggle series visibility. Requires GlobalChartsProvider and chartId to be set.',
},
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

The argType name 'legendInteractive' does not match the actual Legend prop 'interactive'. This prevents the Storybook control from wiring to the component prop. Rename the key to 'interactive' or map it in the story (e.g., pass interactive={legendInteractive}).

Copilot uses AI. Check for mistakes.

Comment on lines +141 to +153
const createKeyDownHandler = useCallback(
( labelText: string ) => {
if ( ! interactive ) {
return undefined;
}
return ( event: React.KeyboardEvent ) => {
if ( event.key === 'Enter' || event.key === ' ' ) {
event.preventDefault();
handleLegendClick( labelText );
}
};
},
[ interactive, handleLegendClick ]
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

React.KeyboardEvent is referenced without importing the React namespace, which will cause 'Cannot find namespace React' in TypeScript. Import the type from 'react' and use it directly: add 'type KeyboardEvent' to the react imports and change the annotation to '(event: KeyboardEvent)'.

Copilot uses AI. Check for mistakes.

&--inactive {
opacity: 0.4;

.legend-item-text {
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

The selector targets '.legend-item-text', but the label element in this component uses the existing '.legend-item-label' class. As a result, the strikethrough will not apply. Update the selector to '.legend-item-label'.

Suggested change
.legend-item-text {
.legend-item-label {

Copilot uses AI. Check for mistakes.


const getHiddenSeries = useCallback(
( chartId: string ) => {
return hiddenSeries.get( chartId ) || new Set();
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

getHiddenSeries returns the internal Set instance from state, allowing external mutation of provider state. Return a defensive copy to avoid accidental mutations: 'const set = hiddenSeries.get(chartId); return set ? new Set(set) : new Set();'.

Suggested change
return hiddenSeries.get( chartId ) || new Set();
const set = hiddenSeries.get( chartId );
return set ? new Set(set) : new Set();

Copilot uses AI. Check for mistakes.

Comment on lines +449 to 455
{ visibleData.map( seriesData => {
// Find original index to maintain consistent colors
const originalIndex = dataSorted.findIndex( s => s.label === seriesData.label );
const { color, lineStyles, glyph } = getElementStyles( {
data: seriesData,
index,
index: originalIndex,
} );
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

[nitpick] Computing originalIndex with findIndex inside the map is O(n^2). Precompute a label->index Map once with useMemo and look up in O(1): 'const indexMap = useMemo(() => new Map(dataSorted.map((s, i) => [s.label, i])), [dataSorted]);' then use 'const originalIndex = indexMap.get(seriesData.label) ?? -1;'.

Copilot uses AI. Check for mistakes.

Comment on lines +359 to +372
{ dataWithVisibleZeros.map( seriesData => {
// Find original index to maintain consistent colors
const originalIndex = dataSorted.findIndex( s => s.label === seriesData.label );
return (
<BarSeries
key={ seriesData?.label }
dataKey={ seriesData?.label }
data={ seriesData.data as DataPointDate[] }
yAccessor={ chartOptions.accessors.yAccessor }
xAccessor={ chartOptions.accessors.xAccessor }
colorAccessor={ getBarBackground( originalIndex ) }
/>
);
} ) }
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

[nitpick] Same O(n^2) pattern from findIndex inside a render map. Use a memoized Map of label->originalIndex (built from dataSorted) and replace findIndex with a direct lookup.

Copilot uses AI. Check for mistakes.

Comment on lines +246 to +252
const dataWithIndex = visibleData.map( d => {
const originalIndex = data.findIndex( item => item.label === d.label );
return {
...d,
index: originalIndex,
};
} );
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

[nitpick] The per-item findIndex is O(n^2). Precompute a Map of original indices: 'const indexMap = useMemo(() => new Map(data.map((item, i) => [item.label, i])), [data]);' then use 'index: indexMap.get(d.label) ?? -1'.

Copilot uses AI. Check for mistakes.

aria-pressed={ interactive ? visible : undefined }
aria-label={
interactive
? `${ label.text }${ visible ? ', visible' : ', hidden' }. Click to toggle.`
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

[nitpick] The aria-label instructs to 'Click to toggle', which excludes keyboard users. Prefer neutral wording like 'Toggle visibility' and state the current status: e.g., ${label.text}: ${visible ? 'visible' : 'hidden'}. Toggle visibility.

Suggested change
? `${ label.text }${ visible ? ', visible' : ', hidden' }. Click to toggle.`
? `${label.text}: ${visible ? 'visible' : 'hidden'}. Toggle visibility.`

Copilot uses AI. Check for mistakes.

Copy link

jp-launch-control bot commented Oct 15, 2025

Code Coverage Summary

Coverage changed in 5 files.

File Coverage Δ% Δ Uncovered
projects/js-packages/charts/src/providers/chart-context/global-charts-provider.tsx 59/71 (83.10%) -16.90% 12 💔
projects/js-packages/charts/src/components/legend/private/base-legend.tsx 32/40 (80.00%) -20.00% 8 💔
projects/js-packages/charts/src/components/bar-chart/bar-chart.tsx 75/82 (91.46%) -0.74% 1 ❤️‍🩹
projects/js-packages/charts/src/components/line-chart/line-chart.tsx 117/126 (92.86%) -0.53% 1 ❤️‍🩹
projects/js-packages/charts/src/components/pie-chart/pie-chart.tsx 79/82 (96.34%) -1.03% 1 ❤️‍🩹

Full summary · PHP report · JS report

If appropriate, add one of these labels to override the failing coverage check: Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR I don't care about code coverage for this PR Use this label to ignore the check for insufficient code coveage.

@kangzj
Copy link
Contributor

kangzj commented Oct 15, 2025

I think it'd be faster if we add this for one chart type. If it works well, we then could easily apply to other charts. What do you think>?

) ) }
{ dataWithVisibleZeros.map( seriesData => {
// Find original index to maintain consistent colors
const originalIndex = dataSorted.findIndex( s => s.label === seriesData.label );
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it might be better to remember the original index from the process started L105 rather than finding them dynamically

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[JS Package] Charts RNA [Status] In Progress [Status] Needs Review This PR is ready for review. [Type] Enhancement Changes to an existing feature — removing, adding, or changing parts of it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants