@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
|
@@ -8,7 +8,11 @@ import clsx from 'clsx';
|
|
|
8
8
|
import { useCallback, useContext, useMemo } from 'react';
|
|
9
9
|
import { Legend, useChartLegendItems } from '../../components/legend';
|
|
10
10
|
import { BaseTooltip } from '../../components/tooltip';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
useDataWithPercentages,
|
|
13
|
+
useInteractiveLegendData,
|
|
14
|
+
usePrefersReducedMotion,
|
|
15
|
+
} from '../../hooks';
|
|
12
16
|
import {
|
|
13
17
|
GlobalChartsProvider,
|
|
14
18
|
useChartId,
|
|
@@ -18,12 +22,19 @@ import {
|
|
|
18
22
|
} from '../../providers';
|
|
19
23
|
import { attachSubComponents } from '../../utils';
|
|
20
24
|
import { ChartSVG, ChartHTML, useChartChildren } from '../private/chart-composition';
|
|
25
|
+
import { ChartLayout } from '../private/chart-layout';
|
|
21
26
|
import { RadialWipeAnimation } from '../private/radial-wipe-animation';
|
|
22
27
|
import { SingleChartContext } from '../private/single-chart-context';
|
|
28
|
+
import { SvgEmptyState } from '../private/svg-empty-state';
|
|
23
29
|
import { withResponsive } from '../private/with-responsive';
|
|
24
30
|
import styles from './pie-semi-circle-chart.module.scss';
|
|
25
31
|
import type { LegendValueDisplay } from '../../components/legend';
|
|
26
|
-
import type {
|
|
32
|
+
import type {
|
|
33
|
+
BaseChartProps,
|
|
34
|
+
DataPointPercentage,
|
|
35
|
+
DataPointPercentageCalculated,
|
|
36
|
+
Optional,
|
|
37
|
+
} from '../../types';
|
|
27
38
|
import type { ChartComponentWithComposition } from '../private/chart-composition';
|
|
28
39
|
import type { ResponsiveConfig } from '../private/with-responsive';
|
|
29
40
|
import type { PieArcDatum } from '@visx/shape/lib/shapes/Pie';
|
|
@@ -34,9 +45,9 @@ import type { FC, MouseEvent, ReactNode } from 'react';
|
|
|
34
45
|
*/
|
|
35
46
|
export type PieSemiCircleChartRenderTooltipParams = {
|
|
36
47
|
/**
|
|
37
|
-
* The data point being hovered, including label, value, and percentage.
|
|
48
|
+
* The data point being hovered, including label, value, and calculated percentage.
|
|
38
49
|
*/
|
|
39
|
-
tooltipData:
|
|
50
|
+
tooltipData: DataPointPercentageCalculated;
|
|
40
51
|
};
|
|
41
52
|
|
|
42
53
|
/**
|
|
@@ -98,13 +109,6 @@ export interface PieSemiCircleChartProps extends BaseChartProps< DataPointPercen
|
|
|
98
109
|
*/
|
|
99
110
|
legendValueDisplay?: LegendValueDisplay;
|
|
100
111
|
|
|
101
|
-
/**
|
|
102
|
-
* Enable interactive legend items that can toggle segment visibility.
|
|
103
|
-
* Requires chartId and GlobalChartsProvider.
|
|
104
|
-
* When segments are hidden, percentages are recalculated so visible segments total 100%.
|
|
105
|
-
*/
|
|
106
|
-
legendInteractive?: boolean;
|
|
107
|
-
|
|
108
112
|
/**
|
|
109
113
|
* Horizontal offset for tooltip positioning in pixels (default: 0)
|
|
110
114
|
*/
|
|
@@ -131,7 +135,7 @@ type PieSemiCircleChartResponsiveComponent = ChartComponentWithComposition<
|
|
|
131
135
|
PieSemiCircleChartBaseProps & ResponsiveConfig
|
|
132
136
|
>;
|
|
133
137
|
|
|
134
|
-
export type ArcData = PieArcDatum<
|
|
138
|
+
export type ArcData = PieArcDatum< DataPointPercentageCalculated >;
|
|
135
139
|
|
|
136
140
|
/**
|
|
137
141
|
* Validates the semi-circle pie chart data
|
|
@@ -144,15 +148,15 @@ const validateData = ( data: DataPointPercentage[] ) => {
|
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
// Check for negative values
|
|
147
|
-
const hasNegativeValues = data.some( item => item.
|
|
151
|
+
const hasNegativeValues = data.some( item => item.value < 0 );
|
|
148
152
|
if ( hasNegativeValues ) {
|
|
149
153
|
return { isValid: false, message: 'Invalid data: Negative values are not allowed' };
|
|
150
154
|
}
|
|
151
155
|
|
|
152
|
-
// Validate total
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
155
|
-
return { isValid: false, message: 'Invalid
|
|
156
|
+
// Validate total value is greater than 0
|
|
157
|
+
const totalValue = data.reduce( ( sum, item ) => sum + item.value, 0 );
|
|
158
|
+
if ( totalValue <= 0 ) {
|
|
159
|
+
return { isValid: false, message: 'Invalid data: Total value must be greater than 0' };
|
|
156
160
|
}
|
|
157
161
|
|
|
158
162
|
return { isValid: true, message: '' };
|
|
@@ -167,15 +171,8 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
167
171
|
clockwise = true,
|
|
168
172
|
withTooltips = false,
|
|
169
173
|
showLegend = false,
|
|
170
|
-
|
|
171
|
-
legendPosition = 'bottom',
|
|
172
|
-
legendAlignment = 'center',
|
|
173
|
-
legendMaxWidth,
|
|
174
|
-
legendTextOverflow = 'wrap',
|
|
175
|
-
legendItemClassName,
|
|
176
|
-
legendShape = 'circle',
|
|
174
|
+
legend = {},
|
|
177
175
|
legendValueDisplay = 'percentage',
|
|
178
|
-
legendInteractive = false,
|
|
179
176
|
label,
|
|
180
177
|
animation,
|
|
181
178
|
note,
|
|
@@ -186,11 +183,12 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
186
183
|
renderTooltip = renderDefaultPieSemiCircleTooltip,
|
|
187
184
|
gap = 'md',
|
|
188
185
|
} ) => {
|
|
186
|
+
const legendInteractive = legend.interactive ?? false;
|
|
187
|
+
const legendPosition = legend.position ?? 'bottom';
|
|
188
|
+
|
|
189
189
|
const chartId = useChartId( providedChartId );
|
|
190
|
-
// Measure the SVG wrapper to calculate constrained dimensions
|
|
191
|
-
const [ svgWrapperRef, svgWrapperWidth, svgWrapperHeight ] = useElementSize< HTMLDivElement >();
|
|
192
190
|
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
|
|
193
|
-
useTooltip<
|
|
191
|
+
useTooltip< DataPointPercentageCalculated >();
|
|
194
192
|
|
|
195
193
|
// Set up portal tooltip for better z-index handling
|
|
196
194
|
// We get containerBounds to cancel out stale offsets in the position calculation
|
|
@@ -245,9 +243,12 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
245
243
|
|
|
246
244
|
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
247
245
|
|
|
246
|
+
// Calculate percentages from values (single source of truth)
|
|
247
|
+
const dataWithPercentages = useDataWithPercentages( data );
|
|
248
|
+
|
|
248
249
|
// Filter and recalculate data for interactive legends
|
|
249
250
|
const { visibleData, allSegmentsHidden, legendData } = useInteractiveLegendData( {
|
|
250
|
-
data,
|
|
251
|
+
data: dataWithPercentages,
|
|
251
252
|
chartId,
|
|
252
253
|
legendInteractive,
|
|
253
254
|
isSeriesVisible,
|
|
@@ -256,12 +257,12 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
256
257
|
// Define accessors with useMemo to avoid changing dependencies
|
|
257
258
|
const accessors = useMemo(
|
|
258
259
|
() => ( {
|
|
259
|
-
value: ( d:
|
|
260
|
+
value: ( d: DataPointPercentageCalculated ) => d.value,
|
|
260
261
|
sort: (
|
|
261
|
-
a:
|
|
262
|
-
b:
|
|
262
|
+
a: DataPointPercentageCalculated & { index: number },
|
|
263
|
+
b: DataPointPercentageCalculated & { index: number }
|
|
263
264
|
) => b.value - a.value,
|
|
264
|
-
fill: ( d:
|
|
265
|
+
fill: ( d: DataPointPercentageCalculated & { index: number } ) =>
|
|
265
266
|
getElementStyles( { data: d, index: d.index } ).color,
|
|
266
267
|
} ),
|
|
267
268
|
[ getElementStyles ]
|
|
@@ -277,7 +278,7 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
277
278
|
const legendItems = useChartLegendItems( legendData, legendOptions );
|
|
278
279
|
|
|
279
280
|
// Process children to extract compound components
|
|
280
|
-
const { svgChildren, htmlChildren, otherChildren } = useChartChildren(
|
|
281
|
+
const { svgChildren, htmlChildren, legendChildren, otherChildren } = useChartChildren(
|
|
281
282
|
children,
|
|
282
283
|
'PieSemiCircleChart'
|
|
283
284
|
);
|
|
@@ -321,18 +322,6 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
321
322
|
);
|
|
322
323
|
}
|
|
323
324
|
|
|
324
|
-
// Calculate chart dimensions maintaining the 2:1 width-to-height ratio.
|
|
325
|
-
// Use measured SVG wrapper dimensions to respect height constraints, falling back
|
|
326
|
-
// to explicit props during initial render before measurement is available.
|
|
327
|
-
const availableWidth = svgWrapperWidth > 0 ? svgWrapperWidth : effectiveWidth;
|
|
328
|
-
const availableHeight =
|
|
329
|
-
svgWrapperHeight > 0 ? svgWrapperHeight : propHeight || effectiveWidth / 2;
|
|
330
|
-
// Constrain width so that height (= width / 2) never exceeds the available height
|
|
331
|
-
const width = Math.min( availableWidth, availableHeight * 2 );
|
|
332
|
-
const height = width / 2;
|
|
333
|
-
const radius = height; // For a semi-circle, radius equals the SVG height
|
|
334
|
-
const innerRadius = radius * ( 1 - thickness );
|
|
335
|
-
|
|
336
325
|
// Map data with index for color assignment
|
|
337
326
|
// When interactive, we need to find the original index to maintain consistent colors
|
|
338
327
|
const dataWithIndex = visibleData.map( d => {
|
|
@@ -349,28 +338,25 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
349
338
|
|
|
350
339
|
const legendElement = showLegend && (
|
|
351
340
|
<Legend
|
|
352
|
-
orientation={
|
|
341
|
+
orientation={ legend.orientation ?? 'horizontal' }
|
|
353
342
|
position={ legendPosition }
|
|
354
|
-
alignment={
|
|
355
|
-
labelStyles={
|
|
356
|
-
itemClassName={
|
|
357
|
-
|
|
343
|
+
alignment={ legend.alignment ?? 'center' }
|
|
344
|
+
labelStyles={ legend.labelStyles }
|
|
345
|
+
itemClassName={ legend.itemClassName }
|
|
346
|
+
itemStyles={ legend.itemStyles }
|
|
347
|
+
shapeStyles={ legend.shapeStyles }
|
|
348
|
+
shape={ legend.shape ?? 'circle' }
|
|
358
349
|
chartId={ chartId }
|
|
359
350
|
interactive={ legendInteractive }
|
|
360
351
|
/>
|
|
361
352
|
);
|
|
362
353
|
|
|
363
354
|
return (
|
|
364
|
-
<SingleChartContext.Provider
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
} }
|
|
370
|
-
>
|
|
371
|
-
<Stack
|
|
372
|
-
ref={ containerRef }
|
|
373
|
-
direction="column"
|
|
355
|
+
<SingleChartContext.Provider value={ { chartId } }>
|
|
356
|
+
<ChartLayout
|
|
357
|
+
legendPosition={ legendPosition }
|
|
358
|
+
legendElement={ legendElement }
|
|
359
|
+
legendChildren={ legendChildren }
|
|
374
360
|
gap={ gap }
|
|
375
361
|
className={ clsx(
|
|
376
362
|
'pie-semi-circle-chart',
|
|
@@ -385,118 +371,130 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
385
371
|
height: propHeight || undefined,
|
|
386
372
|
} }
|
|
387
373
|
data-testid="pie-chart-container"
|
|
374
|
+
trailingContent={
|
|
375
|
+
<>
|
|
376
|
+
{ withTooltips && tooltipOpen && tooltipData && (
|
|
377
|
+
<TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
|
|
378
|
+
<div role="tooltip">{ renderTooltip( { tooltipData } ) }</div>
|
|
379
|
+
</TooltipInPortal>
|
|
380
|
+
) }
|
|
381
|
+
{ htmlChildren }
|
|
382
|
+
{ otherChildren }
|
|
383
|
+
</>
|
|
384
|
+
}
|
|
388
385
|
>
|
|
389
|
-
{
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
{ /* Main chart group centered horizontally and positioned at bottom */ }
|
|
409
|
-
<Group
|
|
410
|
-
top={ height }
|
|
411
|
-
left={ width / 2 }
|
|
412
|
-
mask={ animation && ! prefersReducedMotion ? `url(#radial-wipe-${ chartId })` : null }
|
|
386
|
+
{ ( { contentWidth, contentHeight } ) => {
|
|
387
|
+
// Calculate chart dimensions maintaining the 2:1 width-to-height ratio.
|
|
388
|
+
// Use measured dimensions to respect height constraints, falling back
|
|
389
|
+
// to explicit props during initial render before measurement is available.
|
|
390
|
+
const availableWidth = contentWidth > 0 ? contentWidth : effectiveWidth;
|
|
391
|
+
const availableHeight =
|
|
392
|
+
contentHeight > 0 ? contentHeight : propHeight || effectiveWidth / 2;
|
|
393
|
+
// Constrain width so that height (= width / 2) never exceeds the available height
|
|
394
|
+
const width = Math.min( availableWidth, availableHeight * 2 );
|
|
395
|
+
const height = width / 2;
|
|
396
|
+
const radius = height; // For a semi-circle, radius equals the SVG height
|
|
397
|
+
const innerRadius = radius * ( 1 - thickness );
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<Stack
|
|
401
|
+
ref={ containerRef }
|
|
402
|
+
align="center"
|
|
403
|
+
justify="center"
|
|
404
|
+
className={ styles[ 'pie-semi-circle-chart__centering' ] }
|
|
413
405
|
>
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
'jetpack-charts'
|
|
425
|
-
) }
|
|
426
|
-
</text>
|
|
427
|
-
) : (
|
|
428
|
-
<>
|
|
429
|
-
{ /* Pie chart */ }
|
|
430
|
-
<Pie< DataPointPercentage & { index: number } >
|
|
431
|
-
data={ dataWithIndex }
|
|
432
|
-
pieValue={ accessors.value }
|
|
433
|
-
outerRadius={ radius }
|
|
406
|
+
<svg
|
|
407
|
+
width={ width }
|
|
408
|
+
height={ height }
|
|
409
|
+
viewBox={ `0 0 ${ width } ${ height }` }
|
|
410
|
+
data-testid="pie-chart-svg"
|
|
411
|
+
>
|
|
412
|
+
<defs>
|
|
413
|
+
<RadialWipeAnimation
|
|
414
|
+
id={ `radial-wipe-${ chartId }` }
|
|
415
|
+
radius={ radius }
|
|
434
416
|
innerRadius={ innerRadius }
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
417
|
+
startAngle="-180deg"
|
|
418
|
+
wipePercentage={ 50 }
|
|
419
|
+
/>
|
|
420
|
+
</defs>
|
|
421
|
+
|
|
422
|
+
{ /* Main chart group centered horizontally and positioned at bottom */ }
|
|
423
|
+
<Group
|
|
424
|
+
top={ height }
|
|
425
|
+
left={ width / 2 }
|
|
426
|
+
mask={
|
|
427
|
+
animation && ! prefersReducedMotion ? `url(#radial-wipe-${ chartId })` : null
|
|
428
|
+
}
|
|
429
|
+
>
|
|
430
|
+
{ allSegmentsHidden ? (
|
|
431
|
+
<SvgEmptyState x={ 0 } y={ -radius / 2 } width={ width } height={ height }>
|
|
432
|
+
{ __(
|
|
433
|
+
'All segments are hidden. Click legend items to show data.',
|
|
434
|
+
'jetpack-charts'
|
|
435
|
+
) }
|
|
436
|
+
</SvgEmptyState>
|
|
437
|
+
) : (
|
|
438
|
+
<>
|
|
439
|
+
{ /* Pie chart */ }
|
|
440
|
+
<Pie< DataPointPercentageCalculated & { index: number } >
|
|
441
|
+
data={ dataWithIndex }
|
|
442
|
+
pieValue={ accessors.value }
|
|
443
|
+
outerRadius={ radius }
|
|
444
|
+
innerRadius={ innerRadius }
|
|
445
|
+
cornerRadius={ 3 }
|
|
446
|
+
padAngle={ PAD_ANGLE }
|
|
447
|
+
startAngle={ startAngle }
|
|
448
|
+
endAngle={ endAngle }
|
|
449
|
+
pieSort={ accessors.sort }
|
|
450
|
+
>
|
|
451
|
+
{ pie => {
|
|
452
|
+
return pie.arcs.map( arc => (
|
|
453
|
+
<g
|
|
454
|
+
key={ arc.data.label }
|
|
455
|
+
onMouseMove={ withTooltips ? handleArcMouseMove( arc ) : undefined }
|
|
456
|
+
onMouseLeave={ withTooltips ? handleMouseLeave : undefined }
|
|
457
|
+
>
|
|
458
|
+
<path
|
|
459
|
+
d={ pie.path( arc ) || '' }
|
|
460
|
+
fill={ accessors.fill( arc.data ) }
|
|
461
|
+
data-testid="pie-segment"
|
|
462
|
+
/>
|
|
463
|
+
</g>
|
|
464
|
+
) );
|
|
465
|
+
} }
|
|
466
|
+
</Pie>
|
|
467
|
+
|
|
468
|
+
{ /* Label and note text */ }
|
|
469
|
+
<Group>
|
|
470
|
+
<Text
|
|
471
|
+
textAnchor="middle"
|
|
472
|
+
verticalAnchor="start"
|
|
473
|
+
y={ -40 } // Position above the chart with space for note
|
|
474
|
+
className={ styles.label }
|
|
447
475
|
>
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
<Group>
|
|
460
|
-
<Text
|
|
461
|
-
textAnchor="middle"
|
|
462
|
-
verticalAnchor="start"
|
|
463
|
-
y={ -40 } // Position above the chart with space for note
|
|
464
|
-
className={ styles.label }
|
|
465
|
-
>
|
|
466
|
-
{ label }
|
|
467
|
-
</Text>
|
|
468
|
-
<Text
|
|
469
|
-
textAnchor="middle"
|
|
470
|
-
verticalAnchor="start"
|
|
471
|
-
y={ -20 } // Position between label and chart
|
|
472
|
-
className={ styles.note }
|
|
473
|
-
>
|
|
474
|
-
{ note }
|
|
475
|
-
</Text>
|
|
476
|
-
</Group>
|
|
477
|
-
|
|
478
|
-
{ /* Render SVG children from composition API */ }
|
|
479
|
-
{ ! allSegmentsHidden && svgChildren }
|
|
480
|
-
</>
|
|
481
|
-
) }
|
|
482
|
-
</Group>
|
|
483
|
-
</svg>
|
|
484
|
-
</div>
|
|
485
|
-
|
|
486
|
-
{ legendPosition !== 'top' && legendElement }
|
|
487
|
-
|
|
488
|
-
{ withTooltips && tooltipOpen && tooltipData && (
|
|
489
|
-
<TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
|
|
490
|
-
<div role="tooltip">{ renderTooltip( { tooltipData } ) }</div>
|
|
491
|
-
</TooltipInPortal>
|
|
492
|
-
) }
|
|
493
|
-
|
|
494
|
-
{ /* Render HTML children from composition API */ }
|
|
495
|
-
{ htmlChildren }
|
|
476
|
+
{ label }
|
|
477
|
+
</Text>
|
|
478
|
+
<Text
|
|
479
|
+
textAnchor="middle"
|
|
480
|
+
verticalAnchor="start"
|
|
481
|
+
y={ -20 } // Position between label and chart
|
|
482
|
+
className={ styles.note }
|
|
483
|
+
>
|
|
484
|
+
{ note }
|
|
485
|
+
</Text>
|
|
486
|
+
</Group>
|
|
496
487
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
488
|
+
{ /* Render SVG children from composition API */ }
|
|
489
|
+
{ ! allSegmentsHidden && svgChildren }
|
|
490
|
+
</>
|
|
491
|
+
) }
|
|
492
|
+
</Group>
|
|
493
|
+
</svg>
|
|
494
|
+
</Stack>
|
|
495
|
+
);
|
|
496
|
+
} }
|
|
497
|
+
</ChartLayout>
|
|
500
498
|
</SingleChartContext.Provider>
|
|
501
499
|
);
|
|
502
500
|
};
|
|
@@ -18,21 +18,19 @@ const mockData = [
|
|
|
18
18
|
label: 'Category A',
|
|
19
19
|
value: 30,
|
|
20
20
|
valueDisplay: '30%',
|
|
21
|
-
percentage: 30,
|
|
22
21
|
},
|
|
23
22
|
{
|
|
24
23
|
label: 'Category B',
|
|
25
24
|
value: 70,
|
|
26
25
|
valueDisplay: '70%',
|
|
27
|
-
percentage: 70,
|
|
28
26
|
},
|
|
29
27
|
];
|
|
30
28
|
|
|
31
29
|
// Helper function to render component with providers
|
|
32
|
-
const renderPieChart = props =>
|
|
30
|
+
const renderPieChart = ( props, children = undefined ) =>
|
|
33
31
|
render(
|
|
34
32
|
<GlobalChartsProvider>
|
|
35
|
-
<PieSemiCircleChart { ...props }
|
|
33
|
+
<PieSemiCircleChart { ...props }>{ children }</PieSemiCircleChart>
|
|
36
34
|
</GlobalChartsProvider>
|
|
37
35
|
);
|
|
38
36
|
|
|
@@ -64,9 +62,9 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
64
62
|
it( 'shows tooltip on segment hover when withTooltips is true', async () => {
|
|
65
63
|
const user = userEvent.setup();
|
|
66
64
|
const testData = [
|
|
67
|
-
{ label: 'MacOS', value: 30000, valueDisplay: '30K'
|
|
68
|
-
{ label: 'Linux', value: 22000, valueDisplay: '22K'
|
|
69
|
-
{ label: 'Windows', value: 80000, valueDisplay: '80K'
|
|
65
|
+
{ label: 'MacOS', value: 30000, valueDisplay: '30K' },
|
|
66
|
+
{ label: 'Linux', value: 22000, valueDisplay: '22K' },
|
|
67
|
+
{ label: 'Windows', value: 80000, valueDisplay: '80K' },
|
|
70
68
|
];
|
|
71
69
|
|
|
72
70
|
renderPieChart( { data: testData, withTooltips: true, width: 400 } );
|
|
@@ -86,9 +84,9 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
86
84
|
it( 'hides tooltip on mouse leave', async () => {
|
|
87
85
|
const user = userEvent.setup();
|
|
88
86
|
const testData = [
|
|
89
|
-
{ label: 'MacOS', value: 30000, valueDisplay: '30K'
|
|
90
|
-
{ label: 'Linux', value: 22000, valueDisplay: '22K'
|
|
91
|
-
{ label: 'Windows', value: 80000, valueDisplay: '80K'
|
|
87
|
+
{ label: 'MacOS', value: 30000, valueDisplay: '30K' },
|
|
88
|
+
{ label: 'Linux', value: 22000, valueDisplay: '22K' },
|
|
89
|
+
{ label: 'Windows', value: 80000, valueDisplay: '80K' },
|
|
92
90
|
];
|
|
93
91
|
|
|
94
92
|
renderPieChart( { data: testData, withTooltips: true, width: 400 } );
|
|
@@ -113,9 +111,9 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
113
111
|
it( 'renders custom tooltip when renderTooltip prop is provided', async () => {
|
|
114
112
|
const user = userEvent.setup();
|
|
115
113
|
const testData = [
|
|
116
|
-
{ label: 'MacOS', value: 30000, valueDisplay: '30K'
|
|
117
|
-
{ label: 'Linux', value: 22000, valueDisplay: '22K'
|
|
118
|
-
{ label: 'Windows', value: 80000, valueDisplay: '80K'
|
|
114
|
+
{ label: 'MacOS', value: 30000, valueDisplay: '30K' },
|
|
115
|
+
{ label: 'Linux', value: 22000, valueDisplay: '22K' },
|
|
116
|
+
{ label: 'Windows', value: 80000, valueDisplay: '80K' },
|
|
119
117
|
];
|
|
120
118
|
|
|
121
119
|
const customTooltipRenderer = jest.fn( ( { tooltipData } ) => (
|
|
@@ -148,10 +146,12 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
148
146
|
tooltipData: expect.objectContaining( {
|
|
149
147
|
label: 'MacOS',
|
|
150
148
|
value: 30000,
|
|
151
|
-
percentage: 5,
|
|
152
149
|
} ),
|
|
153
150
|
} )
|
|
154
151
|
);
|
|
152
|
+
// Verify percentage is calculated (approximately 22.73%)
|
|
153
|
+
const callArgs = customTooltipRenderer.mock.calls[ 0 ][ 0 ];
|
|
154
|
+
expect( callArgs.tooltipData.percentage ).toBeCloseTo( 22.73, 1 );
|
|
155
155
|
} );
|
|
156
156
|
|
|
157
157
|
it( 'applies custom className', () => {
|
|
@@ -193,21 +193,21 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
193
193
|
expect( screen.getByText( 'No data available' ) ).toBeInTheDocument();
|
|
194
194
|
} );
|
|
195
195
|
|
|
196
|
-
test( 'handles zero total
|
|
196
|
+
test( 'handles zero total value', () => {
|
|
197
197
|
renderPieChart( {
|
|
198
198
|
data: [
|
|
199
|
-
{ label: 'A', value: 0
|
|
200
|
-
{ label: 'B', value: 0
|
|
199
|
+
{ label: 'A', value: 0 },
|
|
200
|
+
{ label: 'B', value: 0 },
|
|
201
201
|
],
|
|
202
202
|
} );
|
|
203
203
|
expect(
|
|
204
|
-
screen.getByText( 'Invalid
|
|
204
|
+
screen.getByText( 'Invalid data: Total value must be greater than 0' )
|
|
205
205
|
).toBeInTheDocument();
|
|
206
206
|
} );
|
|
207
207
|
|
|
208
208
|
test( 'handles single data point', () => {
|
|
209
209
|
renderPieChart( {
|
|
210
|
-
data: [ { label: 'Single', value: 100
|
|
210
|
+
data: [ { label: 'Single', value: 100 } ],
|
|
211
211
|
} );
|
|
212
212
|
expect( screen.getByTestId( 'pie-segment' ) ).toBeInTheDocument();
|
|
213
213
|
} );
|
|
@@ -215,8 +215,8 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
215
215
|
test( 'handles negative values', () => {
|
|
216
216
|
renderPieChart( {
|
|
217
217
|
data: [
|
|
218
|
-
{ label: 'A', value: -30
|
|
219
|
-
{ label: 'B', value: 130
|
|
218
|
+
{ label: 'A', value: -30 },
|
|
219
|
+
{ label: 'B', value: 130 },
|
|
220
220
|
],
|
|
221
221
|
} );
|
|
222
222
|
expect(
|
|
@@ -256,18 +256,46 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
256
256
|
} );
|
|
257
257
|
} );
|
|
258
258
|
|
|
259
|
+
describe( 'Composition Legend', () => {
|
|
260
|
+
test( 'renders composition legend as child component', () => {
|
|
261
|
+
renderPieChart( { data: mockData }, <PieSemiCircleChart.Legend /> );
|
|
262
|
+
|
|
263
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
264
|
+
expect( screen.getByText( 'Category A' ) ).toBeInTheDocument();
|
|
265
|
+
expect( screen.getByText( 'Category B' ) ).toBeInTheDocument();
|
|
266
|
+
} );
|
|
267
|
+
|
|
268
|
+
test( 'renders composition legend regardless of showLegend value', () => {
|
|
269
|
+
renderPieChart( { data: mockData, showLegend: false }, <PieSemiCircleChart.Legend /> );
|
|
270
|
+
|
|
271
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
272
|
+
} );
|
|
273
|
+
|
|
274
|
+
test( 'renders composition legend in top position', () => {
|
|
275
|
+
renderPieChart( { data: mockData }, <PieSemiCircleChart.Legend position="top" /> );
|
|
276
|
+
|
|
277
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
278
|
+
|
|
279
|
+
// Legend should appear before the chart SVG in DOM order
|
|
280
|
+
const html = document.body.innerHTML;
|
|
281
|
+
expect( html.indexOf( 'data-testid="legend-horizontal"' ) ).toBeLessThan(
|
|
282
|
+
html.indexOf( 'data-testid="pie-chart-svg"' )
|
|
283
|
+
);
|
|
284
|
+
} );
|
|
285
|
+
} );
|
|
286
|
+
|
|
259
287
|
describe( 'Interactive Legend', () => {
|
|
260
288
|
test( 'filters segments when interactive legend is enabled and segment is toggled', async () => {
|
|
261
289
|
const user = userEvent.setup();
|
|
262
290
|
const testData = [
|
|
263
|
-
{ label: 'Segment A', value: 50
|
|
264
|
-
{ label: 'Segment B', value: 50
|
|
291
|
+
{ label: 'Segment A', value: 50 },
|
|
292
|
+
{ label: 'Segment B', value: 50 },
|
|
265
293
|
];
|
|
266
294
|
|
|
267
295
|
renderPieChart( {
|
|
268
296
|
data: testData,
|
|
269
297
|
showLegend: true,
|
|
270
|
-
|
|
298
|
+
legend: { interactive: true },
|
|
271
299
|
chartId: 'test-interactive-semi-circle-chart',
|
|
272
300
|
} );
|
|
273
301
|
|
|
@@ -292,14 +320,14 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
292
320
|
test( 'shows empty state when all segments are hidden', async () => {
|
|
293
321
|
const user = userEvent.setup();
|
|
294
322
|
const testData = [
|
|
295
|
-
{ label: 'Segment A', value: 50
|
|
296
|
-
{ label: 'Segment B', value: 50
|
|
323
|
+
{ label: 'Segment A', value: 50 },
|
|
324
|
+
{ label: 'Segment B', value: 50 },
|
|
297
325
|
];
|
|
298
326
|
|
|
299
327
|
renderPieChart( {
|
|
300
328
|
data: testData,
|
|
301
329
|
showLegend: true,
|
|
302
|
-
|
|
330
|
+
legend: { interactive: true },
|
|
303
331
|
chartId: 'test-all-hidden-semi-circle-chart',
|
|
304
332
|
} );
|
|
305
333
|
|
|
@@ -319,14 +347,14 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
319
347
|
|
|
320
348
|
test( 'does not filter segments when legendInteractive is false', () => {
|
|
321
349
|
const testData = [
|
|
322
|
-
{ label: 'Segment A', value: 50
|
|
323
|
-
{ label: 'Segment B', value: 50
|
|
350
|
+
{ label: 'Segment A', value: 50 },
|
|
351
|
+
{ label: 'Segment B', value: 50 },
|
|
324
352
|
];
|
|
325
353
|
|
|
326
354
|
renderPieChart( {
|
|
327
355
|
data: testData,
|
|
328
356
|
showLegend: true,
|
|
329
|
-
|
|
357
|
+
legend: { interactive: false },
|
|
330
358
|
chartId: 'test-non-interactive-semi-circle-chart',
|
|
331
359
|
} );
|
|
332
360
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { ChartSVG } from './chart-svg';
|
|
2
2
|
export { ChartHTML } from './chart-html';
|
|
3
|
+
export { renderLegendSlot } from './render-legend-slot';
|
|
3
4
|
export { useChartChildren } from './use-chart-children';
|
|
5
|
+
export type { LegendChild } from './use-chart-children';
|
|
4
6
|
export type { BaseChartSubComponents, ChartComponentWithComposition } from './types';
|