Skip to content

Charts: Add standalone Legend component #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

Merged
merged 68 commits into from
Jul 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
a3155a6
Add useChartLegendData hook and types for standalone legend component
annacmc Jul 9, 2025
7f488d8
Add ChartLegend standalone component
annacmc Jul 9, 2025
eb072e6
Refactor LineChart to use useChartLegendData hook
annacmc Jul 9, 2025
301b80a
Refactor BarChart to use useChartLegendData hook
annacmc Jul 9, 2025
0348c8e
Refactor PieChart and PieSemiCircleChart to use useChartLegendData hook
annacmc Jul 9, 2025
36fe909
Add ChartLegend exports to package API
annacmc Jul 9, 2025
f29b9c0
Add comprehensive stories and tests for ChartLegend
annacmc Jul 9, 2025
ad3bb4a
changelog
annacmc Jul 9, 2025
cf952ea
Fix rebase conflicts and Storybook build errors
Jul 11, 2025
f384eb9
Complete ChartLegend component implementation
Jul 11, 2025
dbe4296
Add shared validation utilities
annacmc Jul 14, 2025
5da4e04
Add context-aware Legend component
annacmc Jul 14, 2025
266da39
Refactor useChartLegendData hook for better maintainability
annacmc Jul 14, 2025
16b3593
Update Legend component infrastructure
annacmc Jul 14, 2025
6e8dc22
Export ChartContext for direct usage
annacmc Jul 14, 2025
efbfd05
Replace duplicated validation with shared utilities
annacmc Jul 14, 2025
e786906
Remove separate ChartLegend component
annacmc Jul 14, 2025
0541cde
Update main exports for consolidated legend system
annacmc Jul 14, 2025
53b138d
Add comprehensive Storybook documentation for Legend
annacmc Jul 14, 2025
0ddf4db
Fix linting issues in legend hook
annacmc Jul 14, 2025
a41fd43
Revert "Replace duplicated validation with shared utilities"
annacmc Jul 14, 2025
d1ff1cc
Revert "Add shared validation utilities"
annacmc Jul 14, 2025
2ee14c3
Fix imports after validation revert
annacmc Jul 14, 2025
ff6cf8e
Fix standalone legend reactivity issue
annacmc Jul 14, 2025
385b20d
Fix infinite re-render loop in ChartProvider
annacmc Jul 14, 2025
073be67
Add comprehensive test coverage for Legend component
annacmc Jul 15, 2025
6faf5d8
Fix standalone legend reactivity issue
annacmc Jul 15, 2025
2b8e6da
Fix LineChart to reuse existing ChartProvider context
annacmc Jul 15, 2025
f6fda18
Fix PieChart to reuse existing ChartProvider context
annacmc Jul 15, 2025
fc3e9b2
Add space between legend label and value
annacmc Jul 15, 2025
71643f6
Fix BarChart to reuse existing ChartProvider context
annacmc Jul 15, 2025
57d4fb1
Fix pie chart container overflow issues
annacmc Jul 15, 2025
bacb6c0
Fix showValues default to preserve previous behavior
annacmc Jul 15, 2025
50cc059
Keep necessary lint suppression for useMemo dependencies
annacmc Jul 15, 2025
da0a72f
Add comprehensive unit tests for useChartLegendData hook
annacmc Jul 15, 2025
35691cc
Fix TypeScript errors in CI/CD
annacmc Jul 15, 2025
2b9e9e9
add missing tickLength and
annacmc Jul 15, 2025
cd05c7b
Revert "add missing tickLength and"
annacmc Jul 16, 2025
1d3463e
Remove test additions to reduce PR size
annacmc Jul 16, 2025
291e343
Complete test file removal for PR size reduction
annacmc Jul 16, 2025
5511c11
Clean up unused imports and variables from HighlightTooltip removal
annacmc Jul 16, 2025
557729d
Fix legend regression caused by removed ChartProvider version state
annacmc Jul 16, 2025
d129f52
Merge remote-tracking branch 'origin/trunk' into add/charts-50-standa…
annacmc Jul 17, 2025
973a98b
Fix LineChart context isolation for standalone Legend support
Jul 18, 2025
94cb119
Remove unnecessary function dependencies from ChartProvider context v…
Jul 18, 2025
c230f78
Remove stable functions from useChartRegistration dependencies
Jul 18, 2025
7feb72e
Memoize LineChart legend options to prevent re-renders
Jul 18, 2025
f5317aa
Memoize PieChart legend options to prevent re-renders
Jul 18, 2025
e932790
Memoize BarChart metadata to prevent re-renders
Jul 18, 2025
fd53a5f
Memoize PieSemiCircleChart metadata to prevent re-renders
Jul 18, 2025
c6a1d86
Add useMemo import to chart context tests
Jul 18, 2025
b4210b8
Add clean source code example to standalone legend story
Jul 18, 2025
5a538fc
Merge branch 'trunk' into add/charts-50-standalone-legend-component
annacmc Jul 21, 2025
f46f875
Merge trunk into feature branch
annacmc Jul 23, 2025
d9dd0bd
Refactor ChartProvider to use useState instead of useRef + version co…
annacmc Jul 24, 2025
0df684c
Remove blanket overflow: hidden from withResponsive HOC
annacmc Jul 24, 2025
4964107
Fix withResponsive HOC to respect explicit width/height props
annacmc Jul 24, 2025
54c99cb
Fix PieChart TypeScript props to include BaseChartProps
annacmc Jul 24, 2025
4b4bfde
Remove hardcoded dimensions from dashboard PieChart to enable respons…
annacmc Jul 24, 2025
75df5dc
Remove unnecessary space character from legend value
annacmc Jul 24, 2025
701da72
Merge branch 'trunk' into add/charts-50-standalone-legend-component
annacmc Jul 25, 2025
bfb564a
fix responsive height calculation for line chart
kangzj Jul 25, 2025
cc07742
should probably be better always manipulate the container
kangzj Jul 25, 2025
1f3b3d9
Merge branch 'trunk' into add/charts-50-standalone-legend-component
kangzj Jul 25, 2025
c59730d
make legend implementation consistent
kangzj Jul 25, 2025
7aecb7f
Merge remote-tracking branch 'refs/remotes/origin/add/charts-50-stand…
kangzj Jul 25, 2025
addfb32
revert unnecessary change
kangzj Jul 25, 2025
1682004
fix typing
kangzj Jul 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Charts: adds a standalone chart legend component
52 changes: 31 additions & 21 deletions projects/js-packages/charts/src/components/bar-chart/bar-chart.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { PatternLines, PatternCircles, PatternWaves, PatternHexagons } from '@visx/pattern';
import { Axis, BarSeries, BarGroup, Grid, XYChart } from '@visx/xychart';
import clsx from 'clsx';
import { useCallback, useId, useState, useRef, useMemo } from 'react';
import { useCallback, useContext, useId, useState, useRef, useMemo } from 'react';
import { ChartProvider, useChartId, useChartRegistration } from '../../providers/chart-context';
import { ChartContext } from '../../providers/chart-context/chart-context';
import { useChartTheme, useXYChartTheme } from '../../providers/theme';
import { Legend } from '../legend';
import { useChartLegendData } from '../legend/use-chart-legend-data';
import { useChartDataTransform } from '../shared/use-chart-data-transform';
import { useChartMargin } from '../shared/use-chart-margin';
import { useElementHeight } from '../shared/use-element-height';
Expand Down Expand Up @@ -66,10 +68,14 @@ const BarChartInternal: FC< BarChartProps > = ( {
// Generate a unique chart ID to avoid pattern conflicts with multiple charts
const internalChartId = useId();
const chartId = useChartId( providedChartId );
const providerTheme = useChartTheme();
const theme = useXYChartTheme( data );

const dataSorted = useChartDataTransform( data );

// Create legend items using the reusable hook
const legendItems = useChartLegendData( dataSorted, providerTheme );
Copy link
Preview

Copilot AI Jul 14, 2025

Choose a reason for hiding this comment

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

The default showValues is true, which will display number of data points next to each series, differing from the previous blank values. Add showValues: false to match existing behavior.

Suggested change
const legendItems = useChartLegendData( dataSorted, providerTheme );
const legendItems = useChartLegendData( dataSorted, providerTheme, { showValues: false } );

Copilot uses AI. Check for mistakes.

Copy link
Contributor

Choose a reason for hiding this comment

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

default value for showValues is false as shown here


const chartOptions = useBarChartOptions( dataSorted, horizontal, options );
const defaultMargin = useChartMargin( height, chartOptions, dataSorted, theme, horizontal );
const [ legendRef, legendHeight ] = useElementHeight< HTMLDivElement >();
Expand Down Expand Up @@ -222,24 +228,17 @@ const BarChartInternal: FC< BarChartProps > = ( {
const error = validateData( dataSorted );
const isDataValid = ! error;

// Create legend items (hooks must be called in same order every render)
const legendItems = useMemo(
() =>
dataSorted.map( ( group, index ) => ( {
label: group.label, // Label for each unique group
value: '', // Empty string since we don't want to show a specific value
color: getColor( group, index ),
shapeStyle: group?.options?.legendShapeStyle,
} ) ),
[ dataSorted, getColor ]
// Memoize metadata to prevent unnecessary re-registration
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's centralize the logic to useChartRegistration so that it wouldn't have to be repeated every time.

const chartMetadata = useMemo(
() => ( {
orientation,
withPatterns,
} ),
[ orientation, withPatterns ]
);

// Register chart with context only if data is valid
const providerTheme = useChartTheme();
useChartRegistration( chartId, legendItems, providerTheme, 'bar', isDataValid, {
orientation,
withPatterns,
} );
useChartRegistration( chartId, legendItems, providerTheme, 'bar', isDataValid, chartMetadata );

if ( error ) {
return <div className={ clsx( 'bar-chart', styles[ 'bar-chart' ] ) }>{ error }</div>;
Expand Down Expand Up @@ -347,17 +346,28 @@ const BarChartInternal: FC< BarChartProps > = ( {
className={ styles[ 'bar-chart__legend' ] }
shape={ legendShape }
ref={ legendRef }
chartId={ chartId }
Copy link
Contributor

Choose a reason for hiding this comment

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

I changed BaseLengend to Legend like what's happening in line chart. Please revert if this was intentional.

/>
) }
</div>
);
};

const BarChart: FC< BarChartProps > = props => (
<ChartProvider>
<BarChartInternal { ...props } />
</ChartProvider>
);
const BarChart: FC< BarChartProps > = props => {
const existingContext = useContext( ChartContext );

// If we're already in a ChartProvider context, don't create a new one
if ( existingContext ) {
return <BarChartInternal { ...props } />;
}

// Otherwise, create our own ChartProvider
return (
<ChartProvider>
<BarChartInternal { ...props } />
</ChartProvider>
);
};

BarChart.displayName = 'BarChart';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { forwardRef, useCallback } from 'react';
import { useChartTheme } from '../../providers/theme';
import styles from './legend.module.scss';
import { valueOrIdentity, valueOrIdentityString, labelTransformFactory } from './utils';
import type { LegendProps } from './types';
import type { BaseLegendProps } from './types';

const orientationToFlexDirection = {
horizontal: 'row' as const,
Expand All @@ -17,7 +17,7 @@ const orientationToFlexDirection = {
* Base legend component that displays color-coded items with labels based on visx LegendOrdinal.
* We avoid using LegendOrdinal directly to enable support for advanced features such as interactivity.
*/
export const BaseLegend = forwardRef< HTMLDivElement, LegendProps >(
export const BaseLegend = forwardRef< HTMLDivElement, BaseLegendProps >(
(
{
items,
Expand Down
7 changes: 5 additions & 2 deletions projects/js-packages/charts/src/components/legend/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export { BaseLegend as Legend } from './base-legend';
export type { LegendProps } from './types';
export { Legend } from './legend';
export { BaseLegend } from './base-legend';
export { useChartLegendData } from './use-chart-legend-data';
export type { LegendProps, BaseLegendProps } from './types';
export type { ChartLegendOptions } from './use-chart-legend-data';
25 changes: 25 additions & 0 deletions projects/js-packages/charts/src/components/legend/legend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useContext, useMemo, forwardRef } from 'react';
import { ChartContext } from '../../providers/chart-context/chart-context';
import { BaseLegend } from './base-legend';
import type { LegendProps } from './types';

export const Legend = forwardRef< HTMLDivElement, LegendProps >(
( { chartId, items, ...props }, ref ) => {
// Get context but don't throw if it doesn't exist
const context = useContext( ChartContext );

// Use useMemo to ensure re-rendering when context changes
const contextItems = useMemo( () => {
return chartId && context ? context.getChartData( chartId )?.legendItems : undefined;
}, [ chartId, context ] );

// Use context items if available, otherwise fall back to provided items
const legendItems = ( contextItems || items ) as typeof items;

if ( ! legendItems ) {
return null;
}

return <BaseLegend ref={ ref } items={ legendItems } { ...props } />;
}
);
Loading
Loading