@automattic/charts 0.57.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -2
- package/README.md +7 -54
- package/dist/index.cjs +9607 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +32 -49
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +1612 -33
- package/dist/index.d.ts +1612 -33
- package/dist/index.js +9640 -54
- package/dist/index.js.map +1 -1
- package/package.json +9 -126
- package/src/charts/bar-chart/bar-chart.module.scss +0 -5
- package/src/charts/bar-chart/bar-chart.tsx +142 -149
- package/src/charts/bar-chart/test/bar-chart.test.tsx +48 -31
- package/src/charts/leaderboard-chart/leaderboard-chart.tsx +54 -74
- package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +4 -5
- package/src/charts/leaderboard-chart/types.ts +1 -11
- package/src/charts/line-chart/line-chart.module.scss +0 -5
- package/src/charts/line-chart/line-chart.tsx +202 -193
- package/src/charts/line-chart/private/line-chart-annotations-overlay.tsx +1 -2
- package/src/charts/line-chart/test/line-chart.test.tsx +49 -27
- package/src/charts/line-chart/types.ts +0 -1
- package/src/charts/pie-chart/pie-chart.module.scss +2 -10
- package/src/charts/pie-chart/pie-chart.tsx +212 -212
- package/src/charts/pie-chart/test/composition-api.test.tsx +44 -3
- package/src/charts/pie-chart/test/pie-chart.test.tsx +51 -44
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +2 -8
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +166 -168
- package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +58 -30
- package/src/charts/private/chart-composition/index.ts +2 -0
- package/src/charts/private/chart-composition/render-legend-slot.ts +22 -0
- package/src/charts/private/chart-composition/test/render-legend-slot.test.tsx +60 -0
- package/src/charts/private/chart-composition/test/use-chart-children.test.tsx +91 -0
- package/src/charts/private/chart-composition/use-chart-children.ts +34 -2
- package/src/charts/private/chart-layout/chart-layout.module.scss +7 -0
- package/src/charts/private/chart-layout/chart-layout.tsx +106 -0
- package/src/charts/private/chart-layout/index.ts +2 -0
- package/src/charts/private/chart-layout/test/chart-layout.test.tsx +167 -0
- package/src/charts/private/single-chart-context/single-chart-context.tsx +2 -2
- package/src/charts/private/svg-empty-state/index.ts +1 -0
- package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +7 -0
- package/src/charts/private/svg-empty-state/svg-empty-state.tsx +40 -0
- package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +12 -8
- package/src/components/legend/hooks/use-chart-legend-items.ts +12 -13
- package/src/components/legend/index.ts +1 -8
- package/src/components/legend/legend.tsx +33 -8
- package/src/components/legend/private/base-legend.module.scss +19 -37
- package/src/components/legend/private/base-legend.tsx +0 -2
- package/src/components/legend/test/legend.test.tsx +93 -1
- package/src/components/legend/types.ts +7 -34
- package/src/hooks/index.ts +1 -1
- package/src/hooks/use-data-with-percentages.ts +24 -0
- package/src/hooks/use-interactive-legend-data.ts +18 -15
- package/src/index.ts +66 -9
- package/src/providers/chart-context/global-charts-provider.tsx +7 -1
- package/src/providers/chart-context/hooks/use-chart-registration.ts +2 -1
- package/src/providers/chart-context/types.ts +2 -2
- package/src/types.ts +110 -45
- package/src/utils/date-parsing.ts +10 -1
- package/src/utils/test/date-parsing.test.ts +12 -0
- package/src/utils/test/resolve-css-var.test.ts +4 -2
- package/tsup.config.ts +1 -1
- package/dist/base-tooltip-DOq93wjU.d.cts +0 -38
- package/dist/base-tooltip-DOq93wjU.d.ts +0 -38
- package/dist/charts/bar-chart/index.cjs +0 -15
- package/dist/charts/bar-chart/index.cjs.map +0 -1
- package/dist/charts/bar-chart/index.css +0 -153
- package/dist/charts/bar-chart/index.css.map +0 -1
- package/dist/charts/bar-chart/index.d.cts +0 -37
- package/dist/charts/bar-chart/index.d.ts +0 -37
- package/dist/charts/bar-chart/index.js +0 -15
- package/dist/charts/bar-chart/index.js.map +0 -1
- package/dist/charts/bar-list-chart/index.cjs +0 -16
- package/dist/charts/bar-list-chart/index.cjs.map +0 -1
- package/dist/charts/bar-list-chart/index.css +0 -153
- package/dist/charts/bar-list-chart/index.css.map +0 -1
- package/dist/charts/bar-list-chart/index.d.cts +0 -92
- package/dist/charts/bar-list-chart/index.d.ts +0 -92
- package/dist/charts/bar-list-chart/index.js +0 -16
- package/dist/charts/bar-list-chart/index.js.map +0 -1
- package/dist/charts/conversion-funnel-chart/index.cjs +0 -11
- package/dist/charts/conversion-funnel-chart/index.cjs.map +0 -1
- package/dist/charts/conversion-funnel-chart/index.css +0 -251
- package/dist/charts/conversion-funnel-chart/index.css.map +0 -1
- package/dist/charts/conversion-funnel-chart/index.d.cts +0 -97
- package/dist/charts/conversion-funnel-chart/index.d.ts +0 -97
- package/dist/charts/conversion-funnel-chart/index.js +0 -11
- package/dist/charts/conversion-funnel-chart/index.js.map +0 -1
- package/dist/charts/geo-chart/index.cjs +0 -13
- package/dist/charts/geo-chart/index.cjs.map +0 -1
- package/dist/charts/geo-chart/index.css +0 -117
- package/dist/charts/geo-chart/index.css.map +0 -1
- package/dist/charts/geo-chart/index.d.cts +0 -67
- package/dist/charts/geo-chart/index.d.ts +0 -67
- package/dist/charts/geo-chart/index.js +0 -13
- package/dist/charts/geo-chart/index.js.map +0 -1
- package/dist/charts/leaderboard-chart/index.cjs +0 -20
- package/dist/charts/leaderboard-chart/index.cjs.map +0 -1
- package/dist/charts/leaderboard-chart/index.css +0 -172
- package/dist/charts/leaderboard-chart/index.css.map +0 -1
- package/dist/charts/leaderboard-chart/index.d.cts +0 -46
- package/dist/charts/leaderboard-chart/index.d.ts +0 -46
- package/dist/charts/leaderboard-chart/index.js +0 -20
- package/dist/charts/leaderboard-chart/index.js.map +0 -1
- package/dist/charts/line-chart/index.cjs +0 -15
- package/dist/charts/line-chart/index.cjs.map +0 -1
- package/dist/charts/line-chart/index.css +0 -225
- package/dist/charts/line-chart/index.css.map +0 -1
- package/dist/charts/line-chart/index.d.cts +0 -99
- package/dist/charts/line-chart/index.d.ts +0 -99
- package/dist/charts/line-chart/index.js +0 -15
- package/dist/charts/line-chart/index.js.map +0 -1
- package/dist/charts/pie-chart/index.cjs +0 -18
- package/dist/charts/pie-chart/index.cjs.map +0 -1
- package/dist/charts/pie-chart/index.css +0 -143
- package/dist/charts/pie-chart/index.css.map +0 -1
- package/dist/charts/pie-chart/index.d.cts +0 -97
- package/dist/charts/pie-chart/index.d.ts +0 -97
- package/dist/charts/pie-chart/index.js +0 -18
- package/dist/charts/pie-chart/index.js.map +0 -1
- package/dist/charts/pie-semi-circle-chart/index.cjs +0 -17
- package/dist/charts/pie-semi-circle-chart/index.cjs.map +0 -1
- package/dist/charts/pie-semi-circle-chart/index.css +0 -144
- package/dist/charts/pie-semi-circle-chart/index.css.map +0 -1
- package/dist/charts/pie-semi-circle-chart/index.d.cts +0 -94
- package/dist/charts/pie-semi-circle-chart/index.d.ts +0 -94
- package/dist/charts/pie-semi-circle-chart/index.js +0 -17
- package/dist/charts/pie-semi-circle-chart/index.js.map +0 -1
- package/dist/charts/sparkline/index.cjs +0 -16
- package/dist/charts/sparkline/index.cjs.map +0 -1
- package/dist/charts/sparkline/index.css +0 -242
- package/dist/charts/sparkline/index.css.map +0 -1
- package/dist/charts/sparkline/index.d.cts +0 -113
- package/dist/charts/sparkline/index.d.ts +0 -113
- package/dist/charts/sparkline/index.js +0 -16
- package/dist/charts/sparkline/index.js.map +0 -1
- package/dist/chunk-2A34OA5O.cjs +0 -51
- package/dist/chunk-2A34OA5O.cjs.map +0 -1
- package/dist/chunk-2NCY7R4G.js +0 -3897
- package/dist/chunk-2NCY7R4G.js.map +0 -1
- package/dist/chunk-32DH6JDF.js +0 -1263
- package/dist/chunk-32DH6JDF.js.map +0 -1
- package/dist/chunk-4OPFE4RM.js +0 -614
- package/dist/chunk-4OPFE4RM.js.map +0 -1
- package/dist/chunk-6CCZL2JJ.js +0 -63
- package/dist/chunk-6CCZL2JJ.js.map +0 -1
- package/dist/chunk-77OKCVQN.cjs +0 -421
- package/dist/chunk-77OKCVQN.cjs.map +0 -1
- package/dist/chunk-7FQX4ALL.cjs +0 -219
- package/dist/chunk-7FQX4ALL.cjs.map +0 -1
- package/dist/chunk-ASLARV7L.cjs +0 -81
- package/dist/chunk-ASLARV7L.cjs.map +0 -1
- package/dist/chunk-BCX5THDQ.js +0 -403
- package/dist/chunk-BCX5THDQ.js.map +0 -1
- package/dist/chunk-BPYKWMI7.js +0 -194
- package/dist/chunk-BPYKWMI7.js.map +0 -1
- package/dist/chunk-CZGYJKG6.js +0 -421
- package/dist/chunk-CZGYJKG6.js.map +0 -1
- package/dist/chunk-D2UH4CFE.cjs +0 -120
- package/dist/chunk-D2UH4CFE.cjs.map +0 -1
- package/dist/chunk-DAU3HNEG.js +0 -344
- package/dist/chunk-DAU3HNEG.js.map +0 -1
- package/dist/chunk-H2V4JMSA.js +0 -219
- package/dist/chunk-H2V4JMSA.js.map +0 -1
- package/dist/chunk-I2276W3I.cjs +0 -66
- package/dist/chunk-I2276W3I.cjs.map +0 -1
- package/dist/chunk-I35UYJJR.cjs +0 -468
- package/dist/chunk-I35UYJJR.cjs.map +0 -1
- package/dist/chunk-IU4DYUAV.js +0 -120
- package/dist/chunk-IU4DYUAV.js.map +0 -1
- package/dist/chunk-KXRWNFQJ.js +0 -51
- package/dist/chunk-KXRWNFQJ.js.map +0 -1
- package/dist/chunk-OP6PHB2U.js +0 -81
- package/dist/chunk-OP6PHB2U.js.map +0 -1
- package/dist/chunk-PXLEMUGJ.js +0 -165
- package/dist/chunk-PXLEMUGJ.js.map +0 -1
- package/dist/chunk-RCY6XLGU.cjs +0 -63
- package/dist/chunk-RCY6XLGU.cjs.map +0 -1
- package/dist/chunk-RHHVEJHJ.cjs +0 -1263
- package/dist/chunk-RHHVEJHJ.cjs.map +0 -1
- package/dist/chunk-TO3OQBXG.cjs +0 -165
- package/dist/chunk-TO3OQBXG.cjs.map +0 -1
- package/dist/chunk-V36ERY7Y.js +0 -375
- package/dist/chunk-V36ERY7Y.js.map +0 -1
- package/dist/chunk-VJM5XCB4.cjs +0 -614
- package/dist/chunk-VJM5XCB4.cjs.map +0 -1
- package/dist/chunk-VTS3PNMS.cjs +0 -344
- package/dist/chunk-VTS3PNMS.cjs.map +0 -1
- package/dist/chunk-WLODYNLB.js +0 -1067
- package/dist/chunk-WLODYNLB.js.map +0 -1
- package/dist/chunk-XKRJL2QT.cjs +0 -375
- package/dist/chunk-XKRJL2QT.cjs.map +0 -1
- package/dist/chunk-XWYZIFZW.js +0 -66
- package/dist/chunk-XWYZIFZW.js.map +0 -1
- package/dist/chunk-Y3NNQMAX.cjs +0 -194
- package/dist/chunk-Y3NNQMAX.cjs.map +0 -1
- package/dist/chunk-YE2T52VZ.cjs +0 -1067
- package/dist/chunk-YE2T52VZ.cjs.map +0 -1
- package/dist/chunk-Z26M4V2M.js +0 -468
- package/dist/chunk-Z26M4V2M.js.map +0 -1
- package/dist/chunk-Z45KX47P.cjs +0 -3897
- package/dist/chunk-Z45KX47P.cjs.map +0 -1
- package/dist/chunk-ZH4F5RMG.cjs +0 -403
- package/dist/chunk-ZH4F5RMG.cjs.map +0 -1
- package/dist/components/legend/index.cjs +0 -11
- package/dist/components/legend/index.cjs.map +0 -1
- package/dist/components/legend/index.css +0 -103
- package/dist/components/legend/index.css.map +0 -1
- package/dist/components/legend/index.d.cts +0 -37
- package/dist/components/legend/index.d.ts +0 -37
- package/dist/components/legend/index.js +0 -11
- package/dist/components/legend/index.js.map +0 -1
- package/dist/components/tooltip/index.cjs +0 -12
- package/dist/components/tooltip/index.cjs.map +0 -1
- package/dist/components/tooltip/index.css +0 -13
- package/dist/components/tooltip/index.css.map +0 -1
- package/dist/components/tooltip/index.d.cts +0 -71
- package/dist/components/tooltip/index.d.ts +0 -71
- package/dist/components/tooltip/index.js +0 -12
- package/dist/components/tooltip/index.js.map +0 -1
- package/dist/components/trend-indicator/index.cjs +0 -8
- package/dist/components/trend-indicator/index.cjs.map +0 -1
- package/dist/components/trend-indicator/index.css +0 -27
- package/dist/components/trend-indicator/index.css.map +0 -1
- package/dist/components/trend-indicator/index.d.cts +0 -44
- package/dist/components/trend-indicator/index.d.ts +0 -44
- package/dist/components/trend-indicator/index.js +0 -8
- package/dist/components/trend-indicator/index.js.map +0 -1
- package/dist/format-metric-value-MXm5DtQ_.d.cts +0 -24
- package/dist/format-metric-value-MXm5DtQ_.d.ts +0 -24
- package/dist/hooks/index.cjs +0 -31
- package/dist/hooks/index.cjs.map +0 -1
- package/dist/hooks/index.css +0 -103
- package/dist/hooks/index.css.map +0 -1
- package/dist/hooks/index.d.cts +0 -272
- package/dist/hooks/index.d.ts +0 -272
- package/dist/hooks/index.js +0 -31
- package/dist/hooks/index.js.map +0 -1
- package/dist/leaderboard-chart-BKYYXcg2.d.ts +0 -83
- package/dist/leaderboard-chart-DR7CGb0L.d.cts +0 -83
- package/dist/legend-C2grwnWk.d.cts +0 -9
- package/dist/legend-Cj0xM5dU.d.ts +0 -9
- package/dist/providers/index.cjs +0 -21
- package/dist/providers/index.cjs.map +0 -1
- package/dist/providers/index.css +0 -103
- package/dist/providers/index.css.map +0 -1
- package/dist/providers/index.d.cts +0 -28
- package/dist/providers/index.d.ts +0 -28
- package/dist/providers/index.js +0 -21
- package/dist/providers/index.js.map +0 -1
- package/dist/themes-BmVGrYnF.d.ts +0 -67
- package/dist/themes-CyjKm-P_.d.cts +0 -67
- package/dist/types-CuUEszrM.d.ts +0 -19
- package/dist/types-DZordNiO.d.cts +0 -505
- package/dist/types-DZordNiO.d.ts +0 -505
- package/dist/types-I67mddpr.d.cts +0 -78
- package/dist/types-I67mddpr.d.ts +0 -78
- package/dist/types-KtOPPzPX.d.cts +0 -19
- package/dist/utils/index.cjs +0 -44
- package/dist/utils/index.cjs.map +0 -1
- package/dist/utils/index.d.cts +0 -226
- package/dist/utils/index.d.ts +0 -226
- package/dist/utils/index.js +0 -44
- package/dist/utils/index.js.map +0 -1
- package/dist/with-responsive-CNfhzAUu.d.cts +0 -18
- package/dist/with-responsive-CNfhzAUu.d.ts +0 -18
- package/src/hooks/use-has-legend-child.ts +0 -22
|
@@ -7,7 +7,11 @@ import clsx from 'clsx';
|
|
|
7
7
|
import { useCallback, useContext, useMemo } from 'react';
|
|
8
8
|
import { Legend, useChartLegendItems } from '../../components/legend';
|
|
9
9
|
import { BaseTooltip } from '../../components/tooltip';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
useDataWithPercentages,
|
|
12
|
+
useInteractiveLegendData,
|
|
13
|
+
usePrefersReducedMotion,
|
|
14
|
+
} from '../../hooks';
|
|
11
15
|
import {
|
|
12
16
|
GlobalChartsProvider,
|
|
13
17
|
useChartId,
|
|
@@ -19,12 +23,19 @@ import {
|
|
|
19
23
|
import { attachSubComponents } from '../../utils';
|
|
20
24
|
import { getStringWidth } from '../../visx/text';
|
|
21
25
|
import { ChartSVG, ChartHTML, useChartChildren } from '../private/chart-composition';
|
|
26
|
+
import { ChartLayout } from '../private/chart-layout';
|
|
22
27
|
import { RadialWipeAnimation } from '../private/radial-wipe-animation/';
|
|
23
28
|
import { SingleChartContext } from '../private/single-chart-context';
|
|
29
|
+
import { SvgEmptyState } from '../private/svg-empty-state';
|
|
24
30
|
import { withResponsive, ResponsiveConfig } from '../private/with-responsive';
|
|
25
31
|
import styles from './pie-chart.module.scss';
|
|
26
32
|
import type { LegendValueDisplay } from '../../components/legend';
|
|
27
|
-
import type {
|
|
33
|
+
import type {
|
|
34
|
+
BaseChartProps,
|
|
35
|
+
DataPointPercentage,
|
|
36
|
+
DataPointPercentageCalculated,
|
|
37
|
+
Optional,
|
|
38
|
+
} from '../../types';
|
|
28
39
|
import type { ChartComponentWithComposition } from '../private/chart-composition';
|
|
29
40
|
import type { SVGProps, MouseEvent, ReactNode, FC } from 'react';
|
|
30
41
|
|
|
@@ -33,9 +44,9 @@ import type { SVGProps, MouseEvent, ReactNode, FC } from 'react';
|
|
|
33
44
|
*/
|
|
34
45
|
export type PieChartRenderTooltipParams = {
|
|
35
46
|
/**
|
|
36
|
-
* The data point being hovered, including label, value, and percentage.
|
|
47
|
+
* The data point being hovered, including label, value, and calculated percentage.
|
|
37
48
|
*/
|
|
38
|
-
tooltipData:
|
|
49
|
+
tooltipData: DataPointPercentageCalculated;
|
|
39
50
|
};
|
|
40
51
|
|
|
41
52
|
/**
|
|
@@ -93,13 +104,6 @@ export interface PieChartProps extends BaseChartProps< DataPointPercentage[] > {
|
|
|
93
104
|
*/
|
|
94
105
|
legendValueDisplay?: LegendValueDisplay;
|
|
95
106
|
|
|
96
|
-
/**
|
|
97
|
-
* Enable interactive legend items that can toggle segment visibility.
|
|
98
|
-
* Requires chartId and GlobalChartsProvider.
|
|
99
|
-
* When segments are hidden, percentages are recalculated so visible segments total 100%.
|
|
100
|
-
*/
|
|
101
|
-
legendInteractive?: boolean;
|
|
102
|
-
|
|
103
107
|
/**
|
|
104
108
|
* Use the children prop to render additional elements on the chart.
|
|
105
109
|
*/
|
|
@@ -142,16 +146,15 @@ const validateData = ( data: DataPointPercentage[] ) => {
|
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
// Check for negative values
|
|
145
|
-
const hasNegativeValues = data.some( item => item.
|
|
149
|
+
const hasNegativeValues = data.some( item => item.value < 0 );
|
|
146
150
|
if ( hasNegativeValues ) {
|
|
147
151
|
return { isValid: false, message: 'Invalid data: Negative values are not allowed' };
|
|
148
152
|
}
|
|
149
153
|
|
|
150
|
-
// Validate total
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
return { isValid: false, message: 'Invalid percentage total: Must equal 100' };
|
|
154
|
+
// Validate total value is greater than 0
|
|
155
|
+
const totalValue = data.reduce( ( sum, item ) => sum + item.value, 0 );
|
|
156
|
+
if ( totalValue <= 0 ) {
|
|
157
|
+
return { isValid: false, message: 'Invalid data: Total value must be greater than 0' };
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
return { isValid: true, message: '' };
|
|
@@ -169,13 +172,7 @@ const PieChartInternal = ( {
|
|
|
169
172
|
withTooltips = false,
|
|
170
173
|
className,
|
|
171
174
|
showLegend = false,
|
|
172
|
-
|
|
173
|
-
legendPosition = 'bottom',
|
|
174
|
-
legendAlignment = 'center',
|
|
175
|
-
legendMaxWidth,
|
|
176
|
-
legendTextOverflow = 'wrap',
|
|
177
|
-
legendItemClassName,
|
|
178
|
-
legendShape = 'circle',
|
|
175
|
+
legend = {},
|
|
179
176
|
width: propWidth,
|
|
180
177
|
height: propHeight,
|
|
181
178
|
size,
|
|
@@ -186,18 +183,19 @@ const PieChartInternal = ( {
|
|
|
186
183
|
cornerScale = 0,
|
|
187
184
|
showLabels = true,
|
|
188
185
|
legendValueDisplay = 'percentage',
|
|
189
|
-
legendInteractive = false,
|
|
190
186
|
children = null,
|
|
191
187
|
tooltipOffsetX = 0,
|
|
192
188
|
tooltipOffsetY = -15,
|
|
193
189
|
renderTooltip = renderDefaultPieTooltip,
|
|
194
190
|
gap = 'md',
|
|
195
191
|
}: PieChartProps ) => {
|
|
192
|
+
const legendInteractive = legend.interactive ?? false;
|
|
193
|
+
const legendPosition = legend.position ?? 'bottom';
|
|
194
|
+
|
|
196
195
|
const providerTheme = useGlobalChartsTheme();
|
|
197
196
|
const chartId = useChartId( providedChartId );
|
|
198
|
-
const [ svgWrapperRef, svgWrapperWidth, svgWrapperHeight ] = useElementSize< HTMLDivElement >();
|
|
199
197
|
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
|
|
200
|
-
useTooltip<
|
|
198
|
+
useTooltip< DataPointPercentageCalculated >();
|
|
201
199
|
|
|
202
200
|
// Set up portal tooltip for better z-index handling
|
|
203
201
|
// We get containerBounds to cancel out stale offsets in the position calculation
|
|
@@ -216,9 +214,12 @@ const PieChartInternal = ( {
|
|
|
216
214
|
|
|
217
215
|
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
218
216
|
|
|
217
|
+
// Calculate percentages from values (single source of truth)
|
|
218
|
+
const dataWithPercentages = useDataWithPercentages( data );
|
|
219
|
+
|
|
219
220
|
// Filter and recalculate data for interactive legends
|
|
220
221
|
const { visibleData, allSegmentsHidden, legendData } = useInteractiveLegendData( {
|
|
221
|
-
data,
|
|
222
|
+
data: dataWithPercentages,
|
|
222
223
|
chartId,
|
|
223
224
|
legendInteractive,
|
|
224
225
|
isSeriesVisible,
|
|
@@ -236,7 +237,10 @@ const PieChartInternal = ( {
|
|
|
236
237
|
const { isValid, message } = validateData( data );
|
|
237
238
|
|
|
238
239
|
// Process children to extract compound components
|
|
239
|
-
const { svgChildren, htmlChildren, otherChildren } = useChartChildren(
|
|
240
|
+
const { svgChildren, htmlChildren, legendChildren, otherChildren } = useChartChildren(
|
|
241
|
+
children,
|
|
242
|
+
'PieChart'
|
|
243
|
+
);
|
|
240
244
|
|
|
241
245
|
// Memoize metadata to prevent unnecessary re-registration
|
|
242
246
|
const chartMetadata = useMemo(
|
|
@@ -267,34 +271,9 @@ const PieChartInternal = ( {
|
|
|
267
271
|
);
|
|
268
272
|
}
|
|
269
273
|
|
|
270
|
-
// Calculate the actual pie size:
|
|
271
|
-
// - Measure available space from the svg-wrapper
|
|
272
|
-
// - If size prop provided: use it as max, but shrink if container is smaller
|
|
273
|
-
// - If no size prop: fill available space
|
|
274
|
-
const availableWidth = svgWrapperWidth > 0 ? svgWrapperWidth : 300;
|
|
275
|
-
const availableHeight = svgWrapperHeight > 0 ? svgWrapperHeight : 300;
|
|
276
|
-
const availableSize = Math.min( availableWidth, availableHeight );
|
|
277
|
-
const actualSize = size ? Math.min( size, availableSize ) : availableSize;
|
|
278
|
-
|
|
279
|
-
const width = actualSize;
|
|
280
|
-
const height = actualSize;
|
|
281
|
-
|
|
282
|
-
// Calculate radius based on width/height
|
|
283
|
-
const radius = Math.min( width, height ) / 2;
|
|
284
|
-
|
|
285
|
-
// Center the chart in the available space
|
|
286
|
-
const centerX = width / 2;
|
|
287
|
-
const centerY = height / 2;
|
|
288
|
-
|
|
289
274
|
// Calculate the angle between each (use original data length for consistent spacing)
|
|
290
275
|
const padAngle = gapScale * ( ( 2 * Math.PI ) / data.length );
|
|
291
276
|
|
|
292
|
-
const outerRadius = radius - padding;
|
|
293
|
-
const innerRadius = thickness === 0 ? 0 : outerRadius * ( 1 - thickness );
|
|
294
|
-
|
|
295
|
-
const maxCornerRadius = ( outerRadius - innerRadius ) / 2;
|
|
296
|
-
const cornerRadius = cornerScale ? Math.min( cornerScale * outerRadius, maxCornerRadius ) : 0;
|
|
297
|
-
|
|
298
277
|
// Map the data to include index for color assignment
|
|
299
278
|
// When interactive, we need to find the original index to maintain consistent colors
|
|
300
279
|
const dataWithIndex = visibleData.map( d => {
|
|
@@ -306,36 +285,33 @@ const PieChartInternal = ( {
|
|
|
306
285
|
} );
|
|
307
286
|
|
|
308
287
|
const accessors = {
|
|
309
|
-
value: ( d:
|
|
310
|
-
fill: ( d:
|
|
288
|
+
value: ( d: DataPointPercentageCalculated ) => d.value,
|
|
289
|
+
fill: ( d: DataPointPercentageCalculated & { index: number } ) => {
|
|
311
290
|
return getElementStyles( { data: d, index: d.index } ).color;
|
|
312
291
|
},
|
|
313
292
|
};
|
|
314
293
|
|
|
315
294
|
const legendElement = showLegend && (
|
|
316
295
|
<Legend
|
|
317
|
-
orientation={
|
|
296
|
+
orientation={ legend.orientation ?? 'horizontal' }
|
|
318
297
|
position={ legendPosition }
|
|
319
|
-
alignment={
|
|
320
|
-
labelStyles={
|
|
321
|
-
itemClassName={
|
|
322
|
-
|
|
298
|
+
alignment={ legend.alignment ?? 'center' }
|
|
299
|
+
labelStyles={ legend.labelStyles }
|
|
300
|
+
itemClassName={ legend.itemClassName }
|
|
301
|
+
itemStyles={ legend.itemStyles }
|
|
302
|
+
shapeStyles={ legend.shapeStyles }
|
|
303
|
+
shape={ legend.shape ?? 'circle' }
|
|
323
304
|
chartId={ chartId }
|
|
324
305
|
interactive={ legendInteractive }
|
|
325
306
|
/>
|
|
326
307
|
);
|
|
327
308
|
|
|
328
309
|
return (
|
|
329
|
-
<SingleChartContext.Provider
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
} }
|
|
335
|
-
>
|
|
336
|
-
<Stack
|
|
337
|
-
ref={ containerRef }
|
|
338
|
-
direction="column"
|
|
310
|
+
<SingleChartContext.Provider value={ { chartId } }>
|
|
311
|
+
<ChartLayout
|
|
312
|
+
legendPosition={ legendPosition }
|
|
313
|
+
legendElement={ legendElement }
|
|
314
|
+
legendChildren={ legendChildren }
|
|
339
315
|
gap={ gap }
|
|
340
316
|
className={ clsx(
|
|
341
317
|
'pie-chart',
|
|
@@ -348,153 +324,177 @@ const PieChartInternal = ( {
|
|
|
348
324
|
width: propWidth || undefined,
|
|
349
325
|
height: propHeight || undefined,
|
|
350
326
|
} }
|
|
327
|
+
trailingContent={
|
|
328
|
+
<>
|
|
329
|
+
{ withTooltips && tooltipOpen && tooltipData && (
|
|
330
|
+
<TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
|
|
331
|
+
<div role="tooltip">{ renderTooltip( { tooltipData } ) }</div>
|
|
332
|
+
</TooltipInPortal>
|
|
333
|
+
) }
|
|
334
|
+
{ htmlChildren }
|
|
335
|
+
{ otherChildren }
|
|
336
|
+
</>
|
|
337
|
+
}
|
|
351
338
|
>
|
|
352
|
-
{
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
339
|
+
{ ( { contentWidth, contentHeight } ) => {
|
|
340
|
+
const availableWidth = contentWidth > 0 ? contentWidth : 300;
|
|
341
|
+
const availableHeight = contentHeight > 0 ? contentHeight : 300;
|
|
342
|
+
const availableSize = Math.min( availableWidth, availableHeight );
|
|
343
|
+
const actualSize = size ? Math.min( size, availableSize ) : availableSize;
|
|
344
|
+
|
|
345
|
+
const width = actualSize;
|
|
346
|
+
const height = actualSize;
|
|
347
|
+
|
|
348
|
+
const radius = Math.min( width, height ) / 2;
|
|
349
|
+
const centerX = width / 2;
|
|
350
|
+
const centerY = height / 2;
|
|
351
|
+
|
|
352
|
+
const outerRadius = radius - padding;
|
|
353
|
+
const innerRadius = thickness === 0 ? 0 : outerRadius * ( 1 - thickness );
|
|
354
|
+
|
|
355
|
+
const maxCornerRadius = ( outerRadius - innerRadius ) / 2;
|
|
356
|
+
const cornerRadius = cornerScale
|
|
357
|
+
? Math.min( cornerScale * outerRadius, maxCornerRadius )
|
|
358
|
+
: 0;
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<Stack
|
|
362
|
+
ref={ containerRef }
|
|
363
|
+
align="center"
|
|
364
|
+
justify="center"
|
|
365
|
+
className={ styles[ 'pie-chart__centering' ] }
|
|
373
366
|
>
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
367
|
+
<svg
|
|
368
|
+
viewBox={ `0 0 ${ width } ${ height }` }
|
|
369
|
+
preserveAspectRatio="xMidYMid meet"
|
|
370
|
+
width={ width }
|
|
371
|
+
height={ height }
|
|
372
|
+
>
|
|
373
|
+
<defs>
|
|
374
|
+
<RadialWipeAnimation
|
|
375
|
+
id={ `radial-wipe-${ chartId }` }
|
|
376
|
+
radius={ outerRadius }
|
|
377
|
+
innerRadius={ innerRadius }
|
|
378
|
+
/>
|
|
379
|
+
</defs>
|
|
380
|
+
|
|
381
|
+
<Group
|
|
382
|
+
top={ centerY }
|
|
383
|
+
left={ centerX }
|
|
384
|
+
mask={
|
|
385
|
+
animation && ! prefersReducedMotion ? `url(#radial-wipe-${ chartId })` : null
|
|
386
|
+
}
|
|
381
387
|
>
|
|
382
|
-
{
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
388
|
+
{ allSegmentsHidden ? (
|
|
389
|
+
<SvgEmptyState x={ 0 } y={ 0 } width={ width } height={ height }>
|
|
390
|
+
{ __(
|
|
391
|
+
'All segments are hidden. Click legend items to show data.',
|
|
392
|
+
'jetpack-charts'
|
|
393
|
+
) }
|
|
394
|
+
</SvgEmptyState>
|
|
395
|
+
) : (
|
|
396
|
+
<Pie< DataPointPercentageCalculated & { index: number } >
|
|
397
|
+
data={ dataWithIndex }
|
|
398
|
+
pieValue={ accessors.value }
|
|
399
|
+
outerRadius={ outerRadius }
|
|
400
|
+
innerRadius={ innerRadius }
|
|
401
|
+
padAngle={ padAngle }
|
|
402
|
+
cornerRadius={ cornerRadius }
|
|
403
|
+
>
|
|
404
|
+
{ pie => {
|
|
405
|
+
return pie.arcs.map( ( arc, index ) => {
|
|
406
|
+
const [ centroidX, centroidY ] = pie.path.centroid( arc );
|
|
407
|
+
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.25;
|
|
408
|
+
const handleMouseMove = ( event: MouseEvent< SVGElement > ) => {
|
|
409
|
+
if ( ! withTooltips ) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Don't show tooltip until container bounds are measured
|
|
414
|
+
if ( containerBounds.width === 0 || containerBounds.height === 0 ) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Use clientX/Y and subtract containerBounds to cancel out any stale offset.
|
|
419
|
+
// TooltipInPortal calculates: tooltipLeft + containerBounds.left + scrollX
|
|
420
|
+
// By passing (clientX - containerBounds.left), we get:
|
|
421
|
+
// (clientX - containerBounds.left) + containerBounds.left + scrollX = clientX + scrollX
|
|
422
|
+
// This gives correct page coordinates regardless of stale bounds.
|
|
423
|
+
showTooltip( {
|
|
424
|
+
tooltipData: arc.data,
|
|
425
|
+
tooltipLeft: event.clientX - containerBounds.left + tooltipOffsetX,
|
|
426
|
+
tooltipTop: event.clientY - containerBounds.top + tooltipOffsetY,
|
|
427
|
+
} );
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const pathProps: SVGProps< SVGPathElement > & {
|
|
431
|
+
'data-testid'?: string;
|
|
432
|
+
} = {
|
|
433
|
+
d: pie.path( arc ) || '',
|
|
434
|
+
fill: accessors.fill( arc.data ),
|
|
435
|
+
'data-testid': 'pie-segment',
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const groupProps: SVGProps< SVGGElement > = {};
|
|
439
|
+
if ( withTooltips ) {
|
|
440
|
+
groupProps.onMouseMove = handleMouseMove;
|
|
441
|
+
groupProps.onMouseLeave = onMouseLeave;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Estimate text width more accurately for background sizing
|
|
445
|
+
const fontSize = 12;
|
|
446
|
+
const estimatedTextWidth = getStringWidth( arc.data.label, {
|
|
447
|
+
fontSize,
|
|
448
|
+
} );
|
|
449
|
+
const labelPadding = 6;
|
|
450
|
+
const backgroundWidth = estimatedTextWidth + labelPadding * 2;
|
|
451
|
+
const backgroundHeight = fontSize + labelPadding * 2;
|
|
452
|
+
|
|
453
|
+
return (
|
|
454
|
+
<g key={ `arc-${ index }` } { ...groupProps }>
|
|
455
|
+
<path { ...pathProps } />
|
|
456
|
+
{ showLabels && hasSpaceForLabel && (
|
|
457
|
+
<g>
|
|
458
|
+
{ providerTheme.labelBackgroundColor && (
|
|
459
|
+
<rect
|
|
460
|
+
x={ centroidX - backgroundWidth / 2 }
|
|
461
|
+
y={ centroidY - backgroundHeight / 2 }
|
|
462
|
+
width={ backgroundWidth }
|
|
463
|
+
height={ backgroundHeight }
|
|
464
|
+
fill={ providerTheme.labelBackgroundColor }
|
|
465
|
+
rx={ 4 }
|
|
466
|
+
ry={ 4 }
|
|
467
|
+
pointerEvents="none"
|
|
468
|
+
/>
|
|
469
|
+
) }
|
|
470
|
+
<text
|
|
471
|
+
x={ centroidX }
|
|
472
|
+
y={ centroidY }
|
|
473
|
+
dy=".33em"
|
|
474
|
+
fill={ providerTheme.labelTextColor || '#333' }
|
|
475
|
+
fontSize={ fontSize }
|
|
476
|
+
textAnchor="middle"
|
|
477
|
+
pointerEvents="none"
|
|
478
|
+
>
|
|
479
|
+
{ arc.data.label }
|
|
480
|
+
</text>
|
|
481
|
+
</g>
|
|
457
482
|
) }
|
|
458
|
-
<text
|
|
459
|
-
x={ centroidX }
|
|
460
|
-
y={ centroidY }
|
|
461
|
-
dy=".33em"
|
|
462
|
-
fill={ providerTheme.labelTextColor || '#333' }
|
|
463
|
-
fontSize={ fontSize }
|
|
464
|
-
textAnchor="middle"
|
|
465
|
-
pointerEvents="none"
|
|
466
|
-
>
|
|
467
|
-
{ arc.data.label }
|
|
468
|
-
</text>
|
|
469
483
|
</g>
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
</Pie>
|
|
476
|
-
) }
|
|
477
|
-
|
|
478
|
-
{ /* Render SVG children (like Group, Text) inside the SVG */ }
|
|
479
|
-
{ ! allSegmentsHidden && svgChildren }
|
|
480
|
-
</Group>
|
|
481
|
-
</svg>
|
|
482
|
-
</div>
|
|
483
|
-
|
|
484
|
-
{ legendPosition === 'bottom' && legendElement }
|
|
485
|
-
|
|
486
|
-
{ withTooltips && tooltipOpen && tooltipData && (
|
|
487
|
-
<TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
|
|
488
|
-
<div role="tooltip">{ renderTooltip( { tooltipData } ) }</div>
|
|
489
|
-
</TooltipInPortal>
|
|
490
|
-
) }
|
|
491
|
-
|
|
492
|
-
{ /* Render HTML component children from PieChart.HTML */ }
|
|
493
|
-
{ htmlChildren }
|
|
484
|
+
);
|
|
485
|
+
} );
|
|
486
|
+
} }
|
|
487
|
+
</Pie>
|
|
488
|
+
) }
|
|
494
489
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
490
|
+
{ /* Render SVG children (like Group, Text) inside the SVG */ }
|
|
491
|
+
{ ! allSegmentsHidden && svgChildren }
|
|
492
|
+
</Group>
|
|
493
|
+
</svg>
|
|
494
|
+
</Stack>
|
|
495
|
+
);
|
|
496
|
+
} }
|
|
497
|
+
</ChartLayout>
|
|
498
498
|
</SingleChartContext.Provider>
|
|
499
499
|
);
|
|
500
500
|
};
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { Group } from '@visx/group';
|
|
3
3
|
import '@testing-library/jest-dom';
|
|
4
|
+
import { GlobalChartsProvider } from '../../../providers';
|
|
4
5
|
import { PieChartUnresponsive as PieChart } from '../index';
|
|
5
6
|
|
|
6
7
|
describe( 'PieChart Composition API', () => {
|
|
7
8
|
const mockData = [
|
|
8
|
-
{ label: 'A', value: 30
|
|
9
|
-
{ label: 'B', value: 40
|
|
10
|
-
{ label: 'C', value: 30
|
|
9
|
+
{ label: 'A', value: 30 },
|
|
10
|
+
{ label: 'B', value: 40 },
|
|
11
|
+
{ label: 'C', value: 30 },
|
|
11
12
|
];
|
|
12
13
|
|
|
14
|
+
const renderWithChildren = ( props = {}, children = undefined ) => {
|
|
15
|
+
return render(
|
|
16
|
+
<GlobalChartsProvider>
|
|
17
|
+
<PieChart data={ mockData } size={ 400 } { ...props }>
|
|
18
|
+
{ children }
|
|
19
|
+
</PieChart>
|
|
20
|
+
</GlobalChartsProvider>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
13
24
|
describe( 'Compound Components', () => {
|
|
14
25
|
it( 'renders PieChart.SVG children inside the SVG element', () => {
|
|
15
26
|
render(
|
|
@@ -148,4 +159,34 @@ describe( 'PieChart Composition API', () => {
|
|
|
148
159
|
expect( svg!.contains( newHtml ) ).toBe( false );
|
|
149
160
|
} );
|
|
150
161
|
} );
|
|
162
|
+
|
|
163
|
+
describe( 'Composition Legend', () => {
|
|
164
|
+
test( 'renders composition legend as child component', () => {
|
|
165
|
+
renderWithChildren( {}, <PieChart.Legend /> );
|
|
166
|
+
|
|
167
|
+
const legendItems = screen.getAllByTestId( 'legend-item' );
|
|
168
|
+
expect( legendItems ).toHaveLength( 3 );
|
|
169
|
+
expect( legendItems[ 0 ] ).toHaveTextContent( 'A' );
|
|
170
|
+
expect( legendItems[ 1 ] ).toHaveTextContent( 'B' );
|
|
171
|
+
expect( legendItems[ 2 ] ).toHaveTextContent( 'C' );
|
|
172
|
+
} );
|
|
173
|
+
|
|
174
|
+
test( 'renders composition legend regardless of showLegend value', () => {
|
|
175
|
+
renderWithChildren( { showLegend: false }, <PieChart.Legend /> );
|
|
176
|
+
|
|
177
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 3 );
|
|
178
|
+
} );
|
|
179
|
+
|
|
180
|
+
test( 'renders composition legend in top position', () => {
|
|
181
|
+
renderWithChildren( {}, <PieChart.Legend position="top" /> );
|
|
182
|
+
|
|
183
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 3 );
|
|
184
|
+
|
|
185
|
+
// Legend should appear before the chart SVG in DOM order
|
|
186
|
+
const html = document.body.innerHTML;
|
|
187
|
+
expect( html.indexOf( 'data-testid="legend-horizontal"' ) ).toBeLessThan(
|
|
188
|
+
html.indexOf( 'data-testid="pie-segment"' )
|
|
189
|
+
);
|
|
190
|
+
} );
|
|
191
|
+
} );
|
|
151
192
|
} );
|