From 6be299d6b80c358c270ee802f928a732918025c3 Mon Sep 17 00:00:00 2001
From: Grzegorz Chudzinski-Pawlowski
<112354940+grzegorz-cp@users.noreply.github.com>
Date: Thu, 12 Dec 2024 10:28:09 +1300
Subject: [PATCH] Charts: Adding a theme provider (#40558)
* Charts - Adding a Theme provider and themes
* Charts - Updating theme objects with colours
* Charts - Updating examples
* changelog
* Charts - Fixing double imports
---
.../charts/changelog/add-charts-themes | 4 +
.../charts/src/components/bar-chart/index.tsx | 4 +-
.../bar-chart/stories/index.stories.tsx | 2 +-
.../src/components/line-chart/line-chart.tsx | 65 +++------
.../line-chart/stories/index.stories.tsx | 2 +-
.../pie-semi-circle-chart.tsx | 5 +-
.../stories/index.stories.tsx | 5 +-
.../charts/src/components/shared/types.d.ts | 22 ++-
.../charts/src/providers/theme/index.ts | 2 +
.../providers/theme/stories/index.stories.tsx | 131 ++++++++++++++++++
.../src/providers/theme/theme-provider.tsx | 36 +++++
.../charts/src/providers/theme/themes.ts | 48 +++++++
12 files changed, 268 insertions(+), 58 deletions(-)
create mode 100644 projects/js-packages/charts/changelog/add-charts-themes
create mode 100644 projects/js-packages/charts/src/providers/theme/index.ts
create mode 100644 projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx
create mode 100644 projects/js-packages/charts/src/providers/theme/theme-provider.tsx
create mode 100644 projects/js-packages/charts/src/providers/theme/themes.ts
diff --git a/projects/js-packages/charts/changelog/add-charts-themes b/projects/js-packages/charts/changelog/add-charts-themes
new file mode 100644
index 0000000000000..bed6b167cc232
--- /dev/null
+++ b/projects/js-packages/charts/changelog/add-charts-themes
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Adding a theme provider to Automattic Charts
diff --git a/projects/js-packages/charts/src/components/bar-chart/index.tsx b/projects/js-packages/charts/src/components/bar-chart/index.tsx
index b8d21f7184c3c..41a22493c509d 100644
--- a/projects/js-packages/charts/src/components/bar-chart/index.tsx
+++ b/projects/js-packages/charts/src/components/bar-chart/index.tsx
@@ -5,6 +5,7 @@ import { scaleBand, scaleLinear } from '@visx/scale';
import { Bar } from '@visx/shape';
import { useTooltip } from '@visx/tooltip';
import React from 'react';
+import { useChartTheme } from '../../providers/theme';
import { Tooltip } from '../tooltip';
import type { DataPoint } from '../shared/types';
@@ -28,6 +29,7 @@ function BarChart( { data, width, height, margin, showTooltips = false }: BarCha
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
useTooltip< DataPoint >();
+ const theme = useChartTheme();
const margins = { top: 20, right: 20, bottom: 40, left: 40, ...margin };
const xMax = width - margins.left - margins.right;
const yMax = height - margins.top - margins.bottom;
@@ -80,7 +82,7 @@ function BarChart( { data, width, height, margin, showTooltips = false }: BarCha
y={ yScale( d.value ) }
width={ xScale.bandwidth() }
height={ yMax - ( yScale( d.value ) ?? 0 ) }
- fill="#0675C4"
+ fill={ theme.colors[ 0 ] }
onMouseMove={ getMouseMoveHandler( d ) }
onMouseLeave={ showTooltips ? handleMouseLeave : undefined }
/>
diff --git a/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx
index be5ad4064d28d..3ad3aa21d7f9d 100644
--- a/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx
+++ b/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx
@@ -2,7 +2,7 @@ import BarChart from '../index';
import type { Meta } from '@storybook/react';
export default {
- title: 'JS Packages/Charts/Bar Chart',
+ title: 'JS Packages/Charts/Types/Bar Chart',
component: BarChart,
parameters: {
layout: 'centered',
diff --git a/projects/js-packages/charts/src/components/line-chart/line-chart.tsx b/projects/js-packages/charts/src/components/line-chart/line-chart.tsx
index c6a87bdaa6b12..a7aa6cf8cb9db 100644
--- a/projects/js-packages/charts/src/components/line-chart/line-chart.tsx
+++ b/projects/js-packages/charts/src/components/line-chart/line-chart.tsx
@@ -8,6 +8,7 @@ import {
} from '@visx/xychart';
import clsx from 'clsx';
import { FC } from 'react';
+import { useChartTheme } from '../../providers/theme/theme-provider';
import styles from './line-chart.module.scss';
import type { DataPointDate } from '../shared/types';
@@ -36,52 +37,6 @@ type LineChartProps = {
lineColor?: string;
};
-// TODO: move to a provider
-// const customTheme = buildChartTheme( {
-// // Customize colors
-// colors: [ '#3182ce' ],
-// // Customize typography
-// // labelStyles: {
-// // fill: '#666',
-// // fontSize: 12,
-// // },
-// // Customize grid styles
-// gridStyles: {
-// stroke: '#e2e8f0',
-// strokeWidth: 1,
-// },
-// } );
-
-const customTheme = buildChartTheme( {
- // colors
- backgroundColor: 'lightblue', // used by Tooltip, Annotation
- colors: [ '#3182ce' ], // categorical colors, mapped to series via `dataKey`s
-
- // labels
- // svgLabelBig?: SVGTextProps,
- // svgLabelSmall?: SVGTextProps,
- // htmlLabel?: HTMLTextStyles,
-
- // lines
- // xAxisLineStyles?: LineStyles,
- // yAxisLineStyles?: LineStyles,
- // xTickLineStyles?: LineStyles,
- // yTickLineStyles?: LineStyles,
- // tickLength: number,
-
- // grid
- // gridColor: string,
- // gridColorDark: string, // used for axis baseline if x/yxAxisLineStyles not set
- // gridStyles?: CSSProperties,
- gridStyles: {
- stroke: '#e2e8f0',
- strokeWidth: 1,
- },
- tickLength: 0,
- gridColor: '',
- gridColorDark: '',
-} );
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const renderTooltip: any = ( { tooltipData } ) => {
// TODO: fix any
@@ -117,18 +72,30 @@ const LineChart: FC< LineChartProps > = ( {
width,
height,
margin = { top: 20, right: 20, bottom: 40, left: 40 },
- lineColor = '#3182ce',
} ) => {
+ const providerTheme = useChartTheme();
const accessors = {
xAccessor: ( d: DataPointDate ) => d.date,
yAccessor: ( d: DataPointDate ) => d.value,
};
+ // Use theme to construct XYChart theme
+ const chartTheme = {
+ backgroundColor: providerTheme.backgroundColor,
+ colors: providerTheme.colors,
+ gridStyles: providerTheme.gridStyles,
+ tickLength: providerTheme?.tickLength || 0,
+ gridColor: providerTheme?.gridColor || '',
+ gridColorDark: providerTheme?.gridColorDark || '',
+ };
+
+ const theme = buildChartTheme( chartTheme );
+
//
return (
= ( {
dataKey="Line"
data={ data }
{ ...accessors }
- stroke={ lineColor }
+ stroke={ theme.colors[ 0 ] }
strokeWidth={ 2 }
/>
diff --git a/projects/js-packages/charts/src/components/line-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/components/line-chart/stories/index.stories.tsx
index 1ce2430c18365..40a11b9c67364 100644
--- a/projects/js-packages/charts/src/components/line-chart/stories/index.stories.tsx
+++ b/projects/js-packages/charts/src/components/line-chart/stories/index.stories.tsx
@@ -9,7 +9,7 @@ const data = [
];
export default {
- title: 'JS Packages/Charts/Line Chart',
+ title: 'JS Packages/Charts/Types/Line Chart',
component: LineChart,
parameters: {
layout: 'centered',
diff --git a/projects/js-packages/charts/src/components/pie-semi-circle-chart/pie-semi-circle-chart.tsx b/projects/js-packages/charts/src/components/pie-semi-circle-chart/pie-semi-circle-chart.tsx
index 86340e54eea84..e67868e16adb4 100644
--- a/projects/js-packages/charts/src/components/pie-semi-circle-chart/pie-semi-circle-chart.tsx
+++ b/projects/js-packages/charts/src/components/pie-semi-circle-chart/pie-semi-circle-chart.tsx
@@ -3,6 +3,7 @@ import { Pie } from '@visx/shape';
import { Text } from '@visx/text';
import clsx from 'clsx';
import { FC } from 'react';
+import { useChartTheme } from '../../providers/theme/theme-provider';
import styles from './pie-semi-circle-chart.module.scss';
import type { DataPointPercentage } from '../shared/types';
@@ -38,13 +39,15 @@ const PieSemiCircleChart: FC< PieSemiCircleChartProps > = ( {
label,
note,
} ) => {
+ const providerTheme = useChartTheme();
const centerX = width / 2;
const centerY = height;
const accessors = {
value: d => d.value,
sort: ( a, b ) => a.value - b.value,
- fill: d => d.data.color,
+ // Use the color property from the data object as a last resort. The theme provides colours by default.
+ fill: d => d.color || providerTheme.colors[ d.index ],
};
return (
diff --git a/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.stories.tsx
index 5ebd227447aef..cfc4f50d7453c 100644
--- a/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.stories.tsx
+++ b/projects/js-packages/charts/src/components/pie-semi-circle-chart/stories/index.stories.tsx
@@ -7,26 +7,23 @@ const data = [
value: 80000,
valueDisplay: '$80K',
percentage: 2,
- color: '#3858E9',
},
{
label: 'MacOS',
value: 30000,
valueDisplay: '$30K',
percentage: 5,
- color: '#80C8FF',
},
{
label: 'Linux',
value: 22000,
valueDisplay: '$22K',
percentage: 1,
- color: '#B999FF',
},
];
export default {
- title: 'JS Packages/Charts/Pie Semi Circle Chart',
+ title: 'JS Packages/Charts/Types/Pie Semi Circle Chart',
component: PieSemiCircleChart,
parameters: {
layout: 'centered',
diff --git a/projects/js-packages/charts/src/components/shared/types.d.ts b/projects/js-packages/charts/src/components/shared/types.d.ts
index c4ced7b65e00c..44f2fc9831d57 100644
--- a/projects/js-packages/charts/src/components/shared/types.d.ts
+++ b/projects/js-packages/charts/src/components/shared/types.d.ts
@@ -1,3 +1,5 @@
+import type { CSSProperties } from 'react';
+
export type DataPoint = {
label: string;
value: number;
@@ -26,7 +28,25 @@ export type DataPointPercentage = {
*/
percentage: number;
/**
- * Color code for the segment
+ * Color code for the segment, by default colours are taken from the theme but this property can overrides it
*/
color?: string;
};
+
+/**
+ * Theme configuration for chart components
+ */
+export type ChartTheme = {
+ /** Background color for chart components */
+ backgroundColor: string;
+ /** Array of colors used for data visualization */
+ colors: string[];
+ /** Optional CSS styles for grid lines */
+ gridStyles?: CSSProperties;
+ /** Length of axis ticks in pixels */
+ tickLength: number;
+ /** Color of the grid lines */
+ gridColor: string;
+ /** Color of the grid lines in dark mode */
+ gridColorDark: string;
+};
diff --git a/projects/js-packages/charts/src/providers/theme/index.ts b/projects/js-packages/charts/src/providers/theme/index.ts
new file mode 100644
index 0000000000000..af2ad75002ffc
--- /dev/null
+++ b/projects/js-packages/charts/src/providers/theme/index.ts
@@ -0,0 +1,2 @@
+export { ThemeProvider, useChartTheme } from './theme-provider';
+export { defaultTheme, jetpackTheme, wooTheme } from './themes';
diff --git a/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx b/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx
new file mode 100644
index 0000000000000..69e1cdd50887f
--- /dev/null
+++ b/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx
@@ -0,0 +1,131 @@
+import { Meta, StoryObj } from '@storybook/react';
+import { ThemeProvider, jetpackTheme, wooTheme } from '../.';
+import { LineChart, BarChart, PieSemiCircleChart } from '../../..';
+
+const meta: Meta< typeof LineChart > = {
+ title: 'JS Packages/Charts/Themes',
+ component: ThemeProvider,
+ parameters: {
+ layout: 'centered',
+ },
+};
+
+export default meta;
+type Story = StoryObj< typeof ThemeProvider >;
+
+const sampleData = [
+ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' },
+ { date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
+ { date: new Date( '2024-01-03' ), value: 15, label: 'Jan 3' },
+ { date: new Date( '2024-01-04' ), value: 25, label: 'Jan 4' },
+ { date: new Date( '2024-01-05' ), value: 30, label: 'Jan 5' },
+];
+
+const pieData = [
+ {
+ label: 'Windows',
+ value: 80000,
+ valueDisplay: '80K',
+ percentage: 2,
+ },
+ {
+ label: 'MacOS',
+ value: 30000,
+ valueDisplay: '30K',
+ percentage: 5,
+ },
+ {
+ label: 'Linux',
+ value: 22000,
+ valueDisplay: '22K',
+ percentage: 1,
+ },
+];
+
+const GridComponent = ( { children } ) => {
+ return (
+
+ { children }
+
+ );
+};
+
+export const Default: Story = {
+ render: () => (
+
+
+
+
+
+
+
+ ),
+};
+
+export const JetpackTheme: Story = {
+ render: () => (
+
+
+
+
+
+
+
+ ),
+};
+
+export const WooTheme: Story = {
+ render: () => (
+
+
+
+
+
+
+
+ ),
+};
+
+export const CustomColorTheme: Story = {
+ render: () => (
+
+
+
+
+
+
+
+ ),
+};
diff --git a/projects/js-packages/charts/src/providers/theme/theme-provider.tsx b/projects/js-packages/charts/src/providers/theme/theme-provider.tsx
new file mode 100644
index 0000000000000..88584dda80aae
--- /dev/null
+++ b/projects/js-packages/charts/src/providers/theme/theme-provider.tsx
@@ -0,0 +1,36 @@
+import { createContext, useContext, FC, ReactNode } from 'react';
+import { defaultTheme } from './themes';
+import type { ChartTheme } from '../../components/shared/types';
+
+/**
+ * Context for sharing theme configuration across components
+ */
+const ThemeContext = createContext< ChartTheme >( defaultTheme );
+
+/**
+ * Hook to access chart theme
+ * @return {object} A built theme configuration compatible with visx charts
+ */
+const useChartTheme = () => {
+ const theme = useContext( ThemeContext );
+ return theme;
+};
+
+/**
+ * Props for the ThemeProvider component
+ */
+type ThemeProviderProps = {
+ /** Optional partial theme override */
+ theme?: Partial< ChartTheme >;
+ /** Child components that will have access to the theme */
+ children: ReactNode;
+};
+
+// Provider component for chart theming
+// Allows theme customization through props while maintaining default values
+const ThemeProvider: FC< ThemeProviderProps > = ( { theme = {}, children } ) => {
+ const mergedTheme = { ...defaultTheme, ...theme };
+ return { children };
+};
+
+export { ThemeProvider, useChartTheme };
diff --git a/projects/js-packages/charts/src/providers/theme/themes.ts b/projects/js-packages/charts/src/providers/theme/themes.ts
new file mode 100644
index 0000000000000..b41d14bd845a1
--- /dev/null
+++ b/projects/js-packages/charts/src/providers/theme/themes.ts
@@ -0,0 +1,48 @@
+import type { ChartTheme } from '../../components/shared/types';
+
+/**
+ * Default theme configuration
+ */
+const defaultTheme: ChartTheme = {
+ backgroundColor: '#FFFFFF',
+ colors: [ '#98C8DF', '#006DAB', '#A6DC80', '#1F9828', '#FF8C8F' ],
+ gridStyles: {
+ stroke: '#787C82',
+ strokeWidth: 1,
+ },
+ tickLength: 0,
+ gridColor: '',
+ gridColorDark: '',
+};
+
+/**
+ * Jetpack theme configuration
+ */
+const jetpackTheme: ChartTheme = {
+ backgroundColor: '#FFFFFF',
+ colors: [ '#98C8DF', '#006DAB', '#A6DC80', '#1F9828', '#FF8C8F' ],
+ gridStyles: {
+ stroke: '#787C82',
+ strokeWidth: 1,
+ },
+ tickLength: 0,
+ gridColor: '',
+ gridColorDark: '',
+};
+
+/**
+ * Woo theme configuration
+ */
+const wooTheme: ChartTheme = {
+ backgroundColor: '#FFFFFF',
+ colors: [ '#80C8FF', '#B999FF', '#3858E9' ],
+ gridStyles: {
+ stroke: '#787C82',
+ strokeWidth: 1,
+ },
+ tickLength: 0,
+ gridColor: '',
+ gridColorDark: '',
+};
+
+export { defaultTheme, jetpackTheme, wooTheme };