Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: fixed

Charts: fix label background colour and text colour
43 changes: 32 additions & 11 deletions projects/js-packages/charts/src/components/pie-chart/pie-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,21 +243,42 @@ const PieChartInternal = ( {
pathProps.onMouseLeave = onMouseLeave;
}

// Estimate text width more accurately for background sizing
const fontSize = 12;
const estimatedTextWidth = arc.data.label.length * fontSize * 0.6; // Rough estimate
Copy link
Contributor

@kangzj kangzj Aug 28, 2025

Choose a reason for hiding this comment

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

Could we please try to use getStringWidth instead:

return getStringWidth( longestTick, labelStyle );

const labelPadding = 6;
const backgroundWidth = estimatedTextWidth + labelPadding * 2;
const backgroundHeight = fontSize + labelPadding * 2;

return (
<g key={ `arc-${ index }` }>
<path { ...pathProps } />
{ hasSpaceForLabel && (
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably skip the rendering if the label is set to empty

Copy link
Contributor

Choose a reason for hiding this comment

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

image

<text
x={ centroidX }
y={ centroidY }
dy=".33em"
fill={ providerTheme.labelBackgroundColor || '#333' }
fontSize={ 12 }
textAnchor="middle"
pointerEvents="none"
>
{ arc.data.label }
</text>
<g>
{ providerTheme.labelBackgroundColor && (
<rect
x={ centroidX - backgroundWidth / 2 }
y={ centroidY - backgroundHeight / 2 }
width={ backgroundWidth }
height={ backgroundHeight }
fill={ providerTheme.labelBackgroundColor }
rx={ 4 }
ry={ 4 }
pointerEvents="none"
/>
) }
<text
x={ centroidX }
y={ centroidY }
dy=".33em"
fill={ providerTheme.labelTextColor || '#333' }
fontSize={ fontSize }
textAnchor="middle"
pointerEvents="none"
>
{ arc.data.label }
</text>
</g>
) }
</g>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,39 @@ Override theme colors by providing color values in data:

/>

### Label Colors and Styling

<Canvas of={ PieChartStories.CustomLabelColors } />

The Pie Chart supports customizable label colors through the theme system:

<Source
language="jsx"
code={ `import { ThemeProvider } from '@automattic/charts';

const customTheme = {
...defaultTheme,
labelTextColor: '#FFFFFF', // White text for labels
labelBackgroundColor: 'rgba(0, 0, 0, 0.8)', // Enable dark background (transparent by default)
};

<ThemeProvider theme={ customTheme }>
<PieChart data={ data } />
</ThemeProvider>` }
/>

**Label Color Properties:**

- **`labelTextColor`** - Controls the color of text displayed on pie chart segments. Defaults to `#FFFFFF` (white) to match original behavior.
- **`labelBackgroundColor`** - Controls the background color of labels. Defaults to `transparent` (no background). When set to any color value, creates a rounded rectangle behind each label for enhanced readability. Supports any CSS color including transparency (e.g., `rgba(0, 0, 0, 0.7)`).

**Best Practices:**
- Use high contrast between `labelTextColor` and `labelBackgroundColor` for optimal readability
- Consider semi-transparent backgrounds (`rgba()`) to maintain visual connection to segment colors
- Test with different segment colors to ensure labels remain visible
- Use `labelBackgroundColor` when segment colors are too varied or bright for consistent text visibility
- Consider accessibility guidelines when choosing color combinations

### Theme Integration

Pie Charts automatically integrate with the chart theme system, inheriting colors, typography, and styling from the active theme. Custom themes can be applied using the `ThemeProvider`:
Expand Down Expand Up @@ -282,6 +315,15 @@ Main component for rendering pie and donut charts.
| `aspectRatio` | `number` | `1` | Aspect ratio for responsive charts |
| `resizeDebounceTime` | `number` | `100` | Debounce time for resize events (ms) |

### Theme Properties

The following properties can be customized via the theme system:

| Prop | Type | Default | Description |
| --------------------------- | ------------------------------- | -------------- | --------------------------------------------------------------------------------- |
| `labelTextColor` | `string` | `'#FFFFFF'` | Color of text displayed on pie chart segments |
| `labelBackgroundColor` | `string` | `'transparent'` | Background color for labels. Set to any color value to enable label backgrounds |

### Compound Components

#### PieChart.SVG
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { jetpackTheme, wooTheme } from '../../../providers/theme';
import { jetpackTheme, wooTheme, ThemeProvider } from '../../../providers/theme';
import { sharedDecorator } from '../../../stories/decorator-config';
import { legendArgTypes } from '../../../stories/legend-config';
import { osUsageData as data } from '../../../stories/sample-data';
import { PieChart } from '../index';
import { PieChartUnresponsive } from '../pie-chart';
import type { ChartTheme } from '../../../types';
import type { Meta, StoryObj } from '@storybook/react';

type StoryArgs = React.ComponentProps< typeof PieChart > & {
theme?: string | object;
resize?: string;
containerWidth?: string;
containerHeight?: string;
labelTextColor?: string;
labelBackgroundColor?: string;
};

const meta: Meta< StoryArgs > = {
Expand Down Expand Up @@ -94,6 +97,66 @@ const meta: Meta< StoryArgs > = {
max: 10000,
},
},
labelTextColor: {
control: { type: 'color' },
description: 'Color of the label text displayed on pie chart segments',
},
labelBackgroundColor: {
control: { type: 'color' },
description: 'Background color for labels displayed on pie chart segments',
},
},
render: ( { labelTextColor, labelBackgroundColor, theme, ...args } ) => {
// Create custom theme if label colors are provided
let customTheme: ChartTheme | undefined;
if ( labelTextColor || labelBackgroundColor ) {
let baseTheme: ChartTheme | undefined;

if ( typeof theme === 'object' ) {
baseTheme = theme as ChartTheme;
} else if ( theme === 'jetpack' ) {
baseTheme = jetpackTheme;
} else if ( theme === 'woo' ) {
baseTheme = wooTheme;
}

customTheme = {
...baseTheme,
labelTextColor: labelTextColor || baseTheme?.labelTextColor,
labelBackgroundColor: labelBackgroundColor || baseTheme?.labelBackgroundColor,
} as ChartTheme;
}

let ChartComponent;
if ( customTheme ) {
ChartComponent = (
<ThemeProvider theme={ customTheme }>
<PieChart { ...args } />
</ThemeProvider>
);
} else if ( typeof theme === 'object' ) {
ChartComponent = (
<ThemeProvider theme={ theme as ChartTheme }>
<PieChart { ...args } />
</ThemeProvider>
);
} else if ( theme === 'jetpack' ) {
ChartComponent = (
<ThemeProvider theme={ jetpackTheme }>
<PieChart { ...args } />
</ThemeProvider>
);
} else if ( theme === 'woo' ) {
ChartComponent = (
<ThemeProvider theme={ wooTheme }>
<PieChart { ...args } />
</ThemeProvider>
);
} else {
ChartComponent = <PieChart { ...args } />;
}

return ChartComponent;
},
} satisfies Meta< StoryArgs >;

Expand Down Expand Up @@ -318,6 +381,54 @@ This pattern provides:
},
};

export const CustomLabelColors: Story = {
args: {
...Default.args,
thickness: 0.85, // Slightly thinner for better label visibility
data: [
{
label: 'Desktop',
value: 45000,
valueDisplay: '45K',
percentage: 45,
color: '#FF6B6B', // Light red segment
},
{
label: 'Mobile',
value: 35000,
valueDisplay: '35K',
percentage: 35,
color: '#4ECDC4', // Light teal segment
},
{
label: 'Tablet',
value: 20000,
valueDisplay: '20K',
percentage: 20,
color: '#45B7D1', // Light blue segment
},
],
labelTextColor: '#FFFFFF', // White text for contrast against dark background
labelBackgroundColor: 'rgba(0, 0, 0, 0.75)', // Dark semi-transparent background
size: 400,
},
parameters: {
docs: {
description: {
story: `This example demonstrates how to enable label backgrounds for enhanced readability. By default, labels have no background (transparent) to preserve the original chart appearance, but you can add backgrounds when needed.

**Key Features:**
- **labelTextColor**: White text (\`#FFFFFF\`) for contrast against dark background
- **labelBackgroundColor**: Dark semi-transparent background (\`rgba(0, 0, 0, 0.75)\`) - disabled by default
- **Custom segment colors**: Bright colors that would make default dark text hard to read
- **Opt-in enhancement**: Backgrounds only appear when explicitly set

Use the Storybook controls to experiment with different combinations. Try setting labelBackgroundColor to \`transparent\` to see the default behavior.`,
},
},
},
};

export const ErrorStates: Story = {
render: () => (
<div style={ { display: 'grid', gap: '2rem', gridTemplateColumns: 'repeat(2, 1fr)' } }>
Expand Down
9 changes: 6 additions & 3 deletions projects/js-packages/charts/src/providers/theme/themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import type { ChartTheme } from '../../types';
*/
const defaultTheme: ChartTheme = {
backgroundColor: '#FFFFFF', // chart background color
labelBackgroundColor: '#FFFFFF', // label background color
labelBackgroundColor: 'transparent', // label background color (transparent by default)
labelTextColor: '#FFFFFF', // label text color (white to match original behavior)
colors: [ '#98C8DF', '#006DAB', '#A6DC80', '#1F9828', '#FF8C8F' ],
gridStyles: {
stroke: '#DCDCDE',
Expand Down Expand Up @@ -62,7 +63,8 @@ const defaultTheme: ChartTheme = {
*/
const jetpackTheme: ChartTheme = {
backgroundColor: '#FFFFFF', // chart background color
labelBackgroundColor: '#FFFFFF', // label background color
labelBackgroundColor: 'transparent', // label background color (transparent by default)
labelTextColor: '#FFFFFF', // label text color (white to match original behavior)
colors: [ '#98C8DF', '#006DAB', '#A6DC80', '#1F9828', '#FF8C8F' ],
gridStyles: {
stroke: '#DCDCDE',
Expand Down Expand Up @@ -119,7 +121,8 @@ const jetpackTheme: ChartTheme = {
*/
const wooTheme: ChartTheme = {
backgroundColor: '#FFFFFF', // chart background color
labelBackgroundColor: '#FFFFFF', // label background color
labelBackgroundColor: 'transparent', // label background color (transparent by default)
labelTextColor: '#FFFFFF', // label text color (white to match original behavior)
colors: [ '#80C8FF', '#B999FF', '#3858E9' ],
gridStyles: {
stroke: '#787C82',
Expand Down
2 changes: 2 additions & 0 deletions projects/js-packages/charts/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export type ChartTheme = {
backgroundColor: string;
/** Background color for labels */
labelBackgroundColor?: string;
/** Text color for labels */
labelTextColor?: string;
/** Array of colors used for data visualization */
colors: string[];
/** Optional CSS styles for grid lines */
Expand Down
Loading