diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4af216da4b55c..d2a2a658b2b2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -304,6 +304,9 @@ importers: '@visx/group': specifier: ^3.12.0 version: 3.12.0(react@18.3.1) + '@visx/responsive': + specifier: 3.12.0 + version: 3.12.0(react@18.3.1) '@visx/scale': specifier: ^3.12.0 version: 3.12.0 diff --git a/projects/js-packages/charts/changelog/add-charts-pie-chart b/projects/js-packages/charts/changelog/add-charts-pie-chart new file mode 100644 index 0000000000000..81e8c5063e8ae --- /dev/null +++ b/projects/js-packages/charts/changelog/add-charts-pie-chart @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Adding new chart type - pie chart. diff --git a/projects/js-packages/charts/package.json b/projects/js-packages/charts/package.json index 41e7c7118aaa1..e51243ee01bc2 100644 --- a/projects/js-packages/charts/package.json +++ b/projects/js-packages/charts/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@react-spring/web": "9.7.3", + "@visx/responsive": "3.12.0", "@visx/axis": "^3.12.0", "@visx/group": "^3.12.0", "@visx/scale": "^3.12.0", diff --git a/projects/js-packages/charts/src/components/bar-chart/bar-chart.tsx b/projects/js-packages/charts/src/components/bar-chart/bar-chart.tsx index ee4fe451fe464..984fddd5ac8f4 100644 --- a/projects/js-packages/charts/src/components/bar-chart/bar-chart.tsx +++ b/projects/js-packages/charts/src/components/bar-chart/bar-chart.tsx @@ -5,46 +5,25 @@ import { scaleBand, scaleLinear } from '@visx/scale'; import { Bar } from '@visx/shape'; import { useTooltip } from '@visx/tooltip'; import clsx from 'clsx'; -import { FC, useCallback } from 'react'; +import { FC, useCallback, type MouseEvent } from 'react'; import { useChartTheme } from '../../providers/theme'; import { BaseTooltip } from '../tooltip'; import styles from './bar-chart.module.scss'; -import type { DataPoint } from '../shared/types'; +import type { BaseChartProps, DataPoint } from '../shared/types'; -type BarChartProps = { +interface BarChartProps extends BaseChartProps { /** * Array of data points to display in the chart */ data: DataPoint[]; - /** - * Width of the chart in pixels - */ - width: number; - /** - * Height of the chart in pixels - */ - height: number; - /** - * Chart margins - */ - margin?: { - top?: number; - right?: number; - bottom?: number; - left?: number; - }; - /** - * Whether to show tooltips on hover - */ - showTooltips?: boolean; -}; +} const BarChart: FC< BarChartProps > = ( { data, width, height, margin = { top: 20, right: 20, bottom: 40, left: 40 }, - showTooltips = false, + withTooltips = false, } ) => { const theme = useChartTheme(); const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = @@ -66,7 +45,7 @@ const BarChart: FC< BarChartProps > = ( { } ); const handleMouseMove = useCallback( - ( event: React.MouseEvent, datum: DataPoint ) => { + ( event: MouseEvent< SVGRectElement >, datum: DataPoint ) => { const coords = localPoint( event ); if ( ! coords ) return; @@ -83,35 +62,32 @@ const BarChart: FC< BarChartProps > = ( { hideTooltip(); }, [ hideTooltip ] ); - const handleBarMouseMove = useCallback( - ( d: DataPoint ) => ( event: React.MouseEvent< SVGRectElement > ) => { - handleMouseMove( event, d ); - }, - [ handleMouseMove ] - ); - return (
- { data.map( d => ( - - ) ) } + { data.map( d => { + const handleBarMouseMove = event => handleMouseMove( event, d ); + + return ( + + ); + } ) } - { showTooltips && tooltipOpen && tooltipData && ( + { withTooltips && tooltipOpen && tooltipData && ( {} // eslint-disable-next-line @typescript-eslint/no-explicit-any const renderTooltip: any = ( { tooltipData } ) => { 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 40a11b9c67364..d5c1a7fb6e2fb 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 @@ -1,13 +1,7 @@ import { LineChart } from '../index'; +import sampleData from './sample-data'; import type { Meta } from '@storybook/react'; -const data = [ - { date: new Date( '2023-01-01' ), value: 10 }, - { date: new Date( '2023-02-01' ), value: 20 }, - { date: new Date( '2023-03-01' ), value: 15 }, - { date: new Date( '2023-04-01' ), value: 25 }, -]; - export default { title: 'JS Packages/Charts/Types/Line Chart', component: LineChart, @@ -30,5 +24,5 @@ Default.args = { width: 500, height: 300, margin: { top: 20, right: 20, bottom: 30, left: 40 }, - data, + data: sampleData.mars, }; diff --git a/projects/js-packages/charts/src/components/line-chart/stories/sample-data.ts b/projects/js-packages/charts/src/components/line-chart/stories/sample-data.ts new file mode 100644 index 0000000000000..53d7f8d97d72e --- /dev/null +++ b/projects/js-packages/charts/src/components/line-chart/stories/sample-data.ts @@ -0,0 +1,173 @@ +// Data from UK Met Office (London/Heathrow), Australian Bureau of Meteorology (Canberra), +// and NASA Mars Curiosity Rover (Gale Crater) +const temperatureData = { + london: [ + { date: new Date( '2023-01-01' ), value: 8.2 }, + { date: new Date( '2023-01-08' ), value: 7.9 }, + { date: new Date( '2023-01-15' ), value: 5.1 }, + { date: new Date( '2023-01-22' ), value: 4.8 }, + { date: new Date( '2023-01-29' ), value: 6.3 }, + { date: new Date( '2023-02-05' ), value: 7.2 }, + { date: new Date( '2023-02-12' ), value: 9.4 }, + { date: new Date( '2023-02-19' ), value: 8.7 }, + { date: new Date( '2023-02-26' ), value: 7.1 }, + { date: new Date( '2023-03-05' ), value: 8.3 }, + { date: new Date( '2023-03-12' ), value: 9.5 }, + { date: new Date( '2023-03-19' ), value: 11.2 }, + { date: new Date( '2023-03-26' ), value: 12.8 }, + { date: new Date( '2023-04-02' ), value: 13.4 }, + { date: new Date( '2023-04-09' ), value: 14.1 }, + { date: new Date( '2023-04-16' ), value: 15.3 }, + { date: new Date( '2023-04-23' ), value: 14.8 }, + { date: new Date( '2023-04-30' ), value: 15.7 }, + { date: new Date( '2023-05-07' ), value: 16.9 }, + { date: new Date( '2023-05-14' ), value: 17.2 }, + { date: new Date( '2023-05-21' ), value: 18.4 }, + { date: new Date( '2023-05-28' ), value: 19.1 }, + { date: new Date( '2023-06-04' ), value: 20.3 }, + { date: new Date( '2023-06-11' ), value: 21.5 }, + { date: new Date( '2023-06-18' ), value: 22.8 }, + { date: new Date( '2023-06-25' ), value: 21.9 }, + { date: new Date( '2023-07-02' ), value: 23.1 }, + { date: new Date( '2023-07-09' ), value: 22.7 }, + { date: new Date( '2023-07-16' ), value: 24.2 }, + { date: new Date( '2023-07-23' ), value: 23.8 }, + { date: new Date( '2023-07-30' ), value: 22.9 }, + { date: new Date( '2023-08-06' ), value: 23.4 }, + { date: new Date( '2023-08-13' ), value: 22.8 }, + { date: new Date( '2023-08-20' ), value: 21.9 }, + { date: new Date( '2023-08-27' ), value: 20.7 }, + { date: new Date( '2023-09-03' ), value: 19.8 }, + { date: new Date( '2023-09-10' ), value: 18.9 }, + { date: new Date( '2023-09-17' ), value: 17.6 }, + { date: new Date( '2023-09-24' ), value: 16.8 }, + { date: new Date( '2023-10-01' ), value: 15.9 }, + { date: new Date( '2023-10-08' ), value: 14.7 }, + { date: new Date( '2023-10-15' ), value: 13.8 }, + { date: new Date( '2023-10-22' ), value: 12.9 }, + { date: new Date( '2023-10-29' ), value: 11.7 }, + { date: new Date( '2023-11-05' ), value: 10.8 }, + { date: new Date( '2023-11-12' ), value: 9.9 }, + { date: new Date( '2023-11-19' ), value: 8.7 }, + { date: new Date( '2023-11-26' ), value: 7.8 }, + { date: new Date( '2023-12-03' ), value: 6.9 }, + { date: new Date( '2023-12-10' ), value: 5.8 }, + { date: new Date( '2023-12-17' ), value: 4.9 }, + { date: new Date( '2023-12-24' ), value: 5.7 }, + { date: new Date( '2023-12-31' ), value: 6.2 }, + ], + + canberra: [ + { date: new Date( '2023-01-01' ), value: 28.5 }, + { date: new Date( '2023-01-08' ), value: 29.2 }, + { date: new Date( '2023-01-15' ), value: 30.1 }, + { date: new Date( '2023-01-22' ), value: 29.8 }, + { date: new Date( '2023-01-29' ), value: 28.9 }, + { date: new Date( '2023-02-05' ), value: 27.8 }, + { date: new Date( '2023-02-12' ), value: 26.9 }, + { date: new Date( '2023-02-19' ), value: 25.7 }, + { date: new Date( '2023-02-26' ), value: 24.8 }, + { date: new Date( '2023-03-05' ), value: 23.9 }, + { date: new Date( '2023-03-12' ), value: 22.8 }, + { date: new Date( '2023-03-19' ), value: 21.7 }, + { date: new Date( '2023-03-26' ), value: 20.8 }, + { date: new Date( '2023-04-02' ), value: 19.6 }, + { date: new Date( '2023-04-09' ), value: 18.4 }, + { date: new Date( '2023-04-16' ), value: 17.2 }, + { date: new Date( '2023-04-23' ), value: 16.1 }, + { date: new Date( '2023-04-30' ), value: 15.3 }, + { date: new Date( '2023-05-07' ), value: 14.2 }, + { date: new Date( '2023-05-14' ), value: 13.1 }, + { date: new Date( '2023-05-21' ), value: 12.3 }, + { date: new Date( '2023-05-28' ), value: 11.4 }, + { date: new Date( '2023-06-04' ), value: 10.2 }, + { date: new Date( '2023-06-11' ), value: 9.1 }, + { date: new Date( '2023-06-18' ), value: 8.3 }, + { date: new Date( '2023-06-25' ), value: 7.8 }, + { date: new Date( '2023-07-02' ), value: 7.1 }, + { date: new Date( '2023-07-09' ), value: 6.9 }, + { date: new Date( '2023-07-16' ), value: 7.2 }, + { date: new Date( '2023-07-23' ), value: 8.1 }, + { date: new Date( '2023-07-30' ), value: 9.3 }, + { date: new Date( '2023-08-06' ), value: 10.4 }, + { date: new Date( '2023-08-13' ), value: 11.6 }, + { date: new Date( '2023-08-20' ), value: 12.8 }, + { date: new Date( '2023-08-27' ), value: 13.9 }, + { date: new Date( '2023-09-03' ), value: 15.2 }, + { date: new Date( '2023-09-10' ), value: 16.4 }, + { date: new Date( '2023-09-17' ), value: 17.6 }, + { date: new Date( '2023-09-24' ), value: 18.9 }, + { date: new Date( '2023-10-01' ), value: 20.1 }, + { date: new Date( '2023-10-08' ), value: 21.3 }, + { date: new Date( '2023-10-15' ), value: 22.5 }, + { date: new Date( '2023-10-22' ), value: 23.7 }, + { date: new Date( '2023-10-29' ), value: 24.8 }, + { date: new Date( '2023-11-05' ), value: 25.9 }, + { date: new Date( '2023-11-12' ), value: 26.7 }, + { date: new Date( '2023-11-19' ), value: 27.8 }, + { date: new Date( '2023-11-26' ), value: 28.6 }, + { date: new Date( '2023-12-03' ), value: 29.4 }, + { date: new Date( '2023-12-10' ), value: 30.2 }, + { date: new Date( '2023-12-17' ), value: 29.8 }, + { date: new Date( '2023-12-24' ), value: 28.9 }, + { date: new Date( '2023-12-31' ), value: 29.3 }, + ], + + mars: [ + { date: new Date( '2023-01-01' ), value: -63 }, + { date: new Date( '2023-01-08' ), value: -64 }, + { date: new Date( '2023-01-15' ), value: -65 }, + { date: new Date( '2023-01-22' ), value: -63 }, + { date: new Date( '2023-01-29' ), value: -62 }, + { date: new Date( '2023-02-05' ), value: -60 }, + { date: new Date( '2023-02-12' ), value: -58 }, + { date: new Date( '2023-02-19' ), value: -55 }, + { date: new Date( '2023-02-26' ), value: -52 }, + { date: new Date( '2023-03-05' ), value: -48 }, + { date: new Date( '2023-03-12' ), value: -45 }, + { date: new Date( '2023-03-19' ), value: -42 }, + { date: new Date( '2023-03-26' ), value: -38 }, + { date: new Date( '2023-04-02' ), value: -35 }, + { date: new Date( '2023-04-09' ), value: -32 }, + { date: new Date( '2023-04-16' ), value: -28 }, + { date: new Date( '2023-04-23' ), value: -25 }, + { date: new Date( '2023-04-30' ), value: -22 }, + { date: new Date( '2023-05-07' ), value: -18 }, + { date: new Date( '2023-05-14' ), value: -15 }, + { date: new Date( '2023-05-21' ), value: -12 }, + { date: new Date( '2023-05-28' ), value: -8 }, + { date: new Date( '2023-06-04' ), value: -5 }, + { date: new Date( '2023-06-11' ), value: -2 }, + { date: new Date( '2023-06-18' ), value: 0 }, + { date: new Date( '2023-06-25' ), value: 2 }, + { date: new Date( '2023-07-02' ), value: 5 }, + { date: new Date( '2023-07-09' ), value: 8 }, + { date: new Date( '2023-07-16' ), value: 10 }, + { date: new Date( '2023-07-23' ), value: 12 }, + { date: new Date( '2023-07-30' ), value: 15 }, + { date: new Date( '2023-08-06' ), value: 17 }, + { date: new Date( '2023-08-13' ), value: 20 }, + { date: new Date( '2023-08-20' ), value: 22 }, + { date: new Date( '2023-08-27' ), value: 20 }, + { date: new Date( '2023-09-03' ), value: 18 }, + { date: new Date( '2023-09-10' ), value: 15 }, + { date: new Date( '2023-09-17' ), value: 12 }, + { date: new Date( '2023-09-24' ), value: 8 }, + { date: new Date( '2023-10-01' ), value: 5 }, + { date: new Date( '2023-10-08' ), value: 2 }, + { date: new Date( '2023-10-15' ), value: -2 }, + { date: new Date( '2023-10-22' ), value: -5 }, + { date: new Date( '2023-10-29' ), value: -8 }, + { date: new Date( '2023-11-05' ), value: -12 }, + { date: new Date( '2023-11-12' ), value: -15 }, + { date: new Date( '2023-11-19' ), value: -18 }, + { date: new Date( '2023-11-26' ), value: -22 }, + { date: new Date( '2023-12-03' ), value: -25 }, + { date: new Date( '2023-12-10' ), value: -28 }, + { date: new Date( '2023-12-17' ), value: -32 }, + { date: new Date( '2023-12-24' ), value: -35 }, + { date: new Date( '2023-12-31' ), value: -38 }, + ], +}; + +export default temperatureData; diff --git a/projects/js-packages/charts/src/components/pie-chart/index.tsx b/projects/js-packages/charts/src/components/pie-chart/index.tsx new file mode 100644 index 0000000000000..c5b0025459ea3 --- /dev/null +++ b/projects/js-packages/charts/src/components/pie-chart/index.tsx @@ -0,0 +1 @@ +export { default as PieChart } from './pie-chart'; diff --git a/projects/js-packages/charts/src/components/pie-chart/pie-chart.tsx b/projects/js-packages/charts/src/components/pie-chart/pie-chart.tsx new file mode 100644 index 0000000000000..bffe55ea4de25 --- /dev/null +++ b/projects/js-packages/charts/src/components/pie-chart/pie-chart.tsx @@ -0,0 +1,113 @@ +import { Group } from '@visx/group'; +import { Pie } from '@visx/shape'; +import { SVGProps } from 'react'; +import useChartMouseHandler from '../../hooks/use-chart-mouse-handler'; +import { useChartTheme, defaultTheme } from '../../providers/theme'; +import { Tooltip } from '../tooltip'; +import type { BaseChartProps, DataPoint } from '../shared/types'; + +// TODO: add animation + +interface PieChartProps extends BaseChartProps< DataPoint[] > { + /** + * Inner radius in pixels. If > 0, creates a donut chart. Defaults to 0. + */ + innerRadius?: number; +} + +/** + * Renders a pie or donut chart using the provided data. + * + * @param {PieChartProps} props - Component props + * @return {JSX.Element} The rendered chart component + */ +const PieChart = ( { + data, + width, + height, + withTooltips = false, + innerRadius = 0, +}: PieChartProps ) => { + const providerTheme = useChartTheme(); + const { onMouseMove, onMouseLeave, tooltipOpen, tooltipData, tooltipLeft, tooltipTop } = + useChartMouseHandler( { + withTooltips, + } ); + + // Calculate radius based on width/height + const radius = Math.min( width, height ) / 2; + const centerX = width / 2; + const centerY = height / 2; + + const accessors = { + value: d => d.value, + // 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 ( +
+ + + + { pie => { + return pie.arcs.map( ( arc, index ) => { + const [ centroidX, centroidY ] = pie.path.centroid( arc ); + const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.25; + const handleMouseMove = event => onMouseMove( event, arc.data ); + + const pathProps: SVGProps< SVGPathElement > = { + d: pie.path( arc ) || '', + fill: accessors.fill( arc ), + }; + + if ( withTooltips ) { + pathProps.onMouseMove = handleMouseMove; + pathProps.onMouseLeave = onMouseLeave; + } + + return ( + + + { hasSpaceForLabel && ( + + { arc.data.label } + + ) } + + ); + } ); + } } + + + + { withTooltips && tooltipOpen && tooltipData && ( + + ) } +
+ ); +}; + +export default PieChart; diff --git a/projects/js-packages/charts/src/components/pie-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/components/pie-chart/stories/index.stories.tsx new file mode 100644 index 0000000000000..ddad97895531d --- /dev/null +++ b/projects/js-packages/charts/src/components/pie-chart/stories/index.stories.tsx @@ -0,0 +1,94 @@ +import { ThemeProvider, jetpackTheme, wooTheme } from '../../../providers/theme'; +import { PieChart } from '../index'; +import type { Meta, StoryObj } from '@storybook/react'; + +const data = [ + { label: 'A', value: 30 }, + { label: 'B', value: 20 }, + { label: 'C', value: 15 }, + { label: 'D', value: 35 }, +]; + +type StoryType = StoryObj< typeof PieChart >; + +export default { + title: 'JS Packages/Charts/Types/Pie Chart', + component: PieChart, + parameters: { + layout: 'centered', + }, + argTypes: { + theme: { + control: 'select', + options: { + default: undefined, + jetpack: jetpackTheme, + woo: wooTheme, + }, + defaultValue: undefined, + }, + }, + decorators: [ + ( Story, { args } ) => ( + +
+ +
+
+ ), + ], +} satisfies Meta< typeof PieChart >; + +export const Default: StoryType = { + args: { + width: 400, + height: 400, + withTooltips: false, + data, + theme: 'default', + innerRadius: 0, + }, +}; + +export const Doughnut: StoryType = { + args: { + ...Default.args, + innerRadius: 80, + }, + parameters: { + docs: { + description: { + story: 'Doughnut chart variant with inner radius of 80px.', + }, + }, + }, +}; + +export const WithTooltips: StoryType = { + args: { + ...Default.args, + withTooltips: true, + }, + parameters: { + docs: { + description: { + story: 'Pie chart with interactive tooltips that appear on hover.', + }, + }, + }, +}; + +export const WithTooltipsDoughnut: StoryType = { + args: { + ...Default.args, + withTooltips: true, + innerRadius: 100, + }, + parameters: { + docs: { + description: { + story: 'Doughnut chart with interactive tooltips that appear on hover.', + }, + }, + }, +}; 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 988e15412b003..4eb3b27ed3263 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 @@ -8,23 +8,9 @@ import { FC, useCallback } from 'react'; import { useChartTheme } from '../../providers/theme/theme-provider'; import { BaseTooltip } from '../tooltip'; import styles from './pie-semi-circle-chart.module.scss'; -import type { DataPointPercentage } from '../shared/types'; +import type { BaseChartProps, DataPointPercentage } from '../shared/types'; -type ArcData = PieArcDatum< DataPointPercentage >; - -interface PieSemiCircleChartProps { - /** - * Array of data points to display in the chart - */ - data: DataPointPercentage[]; - /** - * Width of the chart in pixels - */ - width: number; - /** - * Height of the chart in pixels - */ - height: number; +interface PieSemiCircleChartProps extends BaseChartProps< DataPointPercentage[] > { /** * Label text to display above the chart */ @@ -33,19 +19,17 @@ interface PieSemiCircleChartProps { * Note text to display below the label */ note: string; - /** - * Whether to show tooltips - */ - showTooltips?: boolean; } +type ArcData = PieArcDatum< DataPointPercentage >; + const PieSemiCircleChart: FC< PieSemiCircleChartProps > = ( { data, width, height, label, note, - showTooltips = false, + withTooltips = false, } ) => { const providerTheme = useChartTheme(); const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = @@ -106,7 +90,7 @@ const PieSemiCircleChart: FC< PieSemiCircleChartProps > = ( { data={ dataWithIndex } pieValue={ accessors.value } outerRadius={ width / 2 } // half of the diameter (width) - innerRadius={ ( width / 2 ) * 0.6 } // 70% of the radius + innerRadius={ ( width / 2 ) * 0.6 } // 60% of the radius cornerRadius={ 3 } padAngle={ 0.03 } startAngle={ -Math.PI / 2 } @@ -147,7 +131,7 @@ const PieSemiCircleChart: FC< PieSemiCircleChartProps > = ( { - { showTooltips && tooltipOpen && tooltipData && ( + { withTooltips && tooltipOpen && tooltipData && ( = { + /** + * Array of data points to display in the chart + */ + data: T extends DataPoint | DataPointDate ? T[] : T; + /** + * Width of the chart in pixels + */ + width: number; + /** + * Height of the chart in pixels + */ + height: number; + /** + * Chart margins + */ + margin?: { + top: number; + right: number; + bottom: number; + left: number; + }; + /** + * Whether to show tooltips on hover. False by default. + */ + withTooltips?: boolean; +}; diff --git a/projects/js-packages/charts/src/hooks/use-chart-mouse-handler.ts b/projects/js-packages/charts/src/hooks/use-chart-mouse-handler.ts new file mode 100644 index 0000000000000..8a1739a90e4ec --- /dev/null +++ b/projects/js-packages/charts/src/hooks/use-chart-mouse-handler.ts @@ -0,0 +1,90 @@ +import { localPoint } from '@visx/event'; +import { useTooltip } from '@visx/tooltip'; +import { useCallback, type MouseEvent } from 'react'; +import type { DataPoint } from '../components/shared/types'; + +type UseChartMouseHandlerProps = { + /** + * Whether tooltips are enabled + */ + withTooltips: boolean; +}; + +type UseChartMouseHandlerReturn = { + /** + * Handler for mouse move events + */ + onMouseMove: ( event: React.MouseEvent< SVGElement >, data: DataPoint ) => void; + /** + * Handler for mouse leave events + */ + onMouseLeave: () => void; + /** + * Whether the tooltip is currently open + */ + tooltipOpen: boolean; + /** + * The current tooltip data + */ + tooltipData: DataPoint | null; + /** + * The current tooltip left position + */ + tooltipLeft: number | undefined; + /** + * The current tooltip top position + */ + tooltipTop: number | undefined; +}; + +/** + * Hook to handle mouse interactions for chart components + * + * @param {UseChartMouseHandlerProps} props - Hook configuration + * @return {UseChartMouseHandlerReturn} Object containing handlers and tooltip state + */ +const useChartMouseHandler = ( { + withTooltips, +}: UseChartMouseHandlerProps ): UseChartMouseHandlerReturn => { + const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = + useTooltip< DataPoint >(); + + // TODO: either debounce/throttle or use useTooltipInPortal with built-in debounce + const onMouseMove = useCallback( + ( event: MouseEvent< SVGElement >, data: DataPoint ) => { + if ( ! withTooltips ) { + return; + } + + const coords = localPoint( event ); + if ( ! coords ) { + return; + } + + showTooltip( { + tooltipData: data, + tooltipLeft: coords.x, + tooltipTop: coords.y - 10, + } ); + }, + [ withTooltips, showTooltip ] + ); + + const onMouseLeave = useCallback( () => { + if ( ! withTooltips ) { + return; + } + hideTooltip(); + }, [ withTooltips, hideTooltip ] ); + + return { + onMouseMove, + onMouseLeave, + tooltipOpen, + tooltipData, + tooltipLeft, + tooltipTop, + }; +}; + +export default useChartMouseHandler; diff --git a/projects/js-packages/charts/src/index.ts b/projects/js-packages/charts/src/index.ts index 8dc8f3221948a..b52a51461252c 100644 --- a/projects/js-packages/charts/src/index.ts +++ b/projects/js-packages/charts/src/index.ts @@ -1,6 +1,17 @@ +// Charts export { default as BarChart } from './components/bar-chart'; export { LineChart } from './components/line-chart'; +export { PieChart } from './components/pie-chart'; export { PieSemiCircleChart } from './components/pie-semi-circle-chart'; -export type * from './components/shared/types'; + +// Chart components export { BaseTooltip } from './components/tooltip'; + +// Providers +export { ThemeProvider } from './providers/theme'; + +// Hooks + +// Types +export type * from './components/shared/types'; export type { BaseTooltipProps } from './components/tooltip'; diff --git a/projects/js-packages/charts/src/providers/theme/themes.ts b/projects/js-packages/charts/src/providers/theme/themes.ts index b41d14bd845a1..58bcf3c3fcb31 100644 --- a/projects/js-packages/charts/src/providers/theme/themes.ts +++ b/projects/js-packages/charts/src/providers/theme/themes.ts @@ -4,7 +4,8 @@ import type { ChartTheme } from '../../components/shared/types'; * Default theme configuration */ const defaultTheme: ChartTheme = { - backgroundColor: '#FFFFFF', + backgroundColor: '#FFFFFF', // chart background color + labelBackgroundColor: '#FFFFFF', // label background color colors: [ '#98C8DF', '#006DAB', '#A6DC80', '#1F9828', '#FF8C8F' ], gridStyles: { stroke: '#787C82', @@ -19,7 +20,8 @@ const defaultTheme: ChartTheme = { * Jetpack theme configuration */ const jetpackTheme: ChartTheme = { - backgroundColor: '#FFFFFF', + backgroundColor: '#FFFFFF', // chart background color + labelBackgroundColor: '#FFFFFF', // label background color colors: [ '#98C8DF', '#006DAB', '#A6DC80', '#1F9828', '#FF8C8F' ], gridStyles: { stroke: '#787C82', @@ -34,7 +36,8 @@ const jetpackTheme: ChartTheme = { * Woo theme configuration */ const wooTheme: ChartTheme = { - backgroundColor: '#FFFFFF', + backgroundColor: '#FFFFFF', // chart background color + labelBackgroundColor: '#FFFFFF', // label background color colors: [ '#80C8FF', '#B999FF', '#3858E9' ], gridStyles: { stroke: '#787C82',