@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { renderHook } from '@testing-library/react';
|
|
2
2
|
import { GlobalChartsProvider } from '../../../../providers';
|
|
3
3
|
import { useChartLegendItems } from '../use-chart-legend-items';
|
|
4
|
-
import type {
|
|
4
|
+
import type { DataPointPercentageCalculated, DataPointDate, SeriesData } from '../../../../types';
|
|
5
5
|
import type { ReactNode } from 'react';
|
|
6
6
|
|
|
7
7
|
// Wrapper component to provide GlobalChartsProvider context
|
|
@@ -11,8 +11,8 @@ const wrapper = ( { children }: { children: ReactNode } ) => (
|
|
|
11
11
|
|
|
12
12
|
describe( 'useChartLegendItems', () => {
|
|
13
13
|
describe( 'Number Formatting (i18n)', () => {
|
|
14
|
-
describe( '
|
|
15
|
-
const percentageData:
|
|
14
|
+
describe( 'DataPointPercentageCalculated', () => {
|
|
15
|
+
const percentageData: DataPointPercentageCalculated[] = [
|
|
16
16
|
{ label: 'Item 1', value: 80000, percentage: 60.6 },
|
|
17
17
|
{ label: 'Item 2', value: 30000, percentage: 22.7 },
|
|
18
18
|
{ label: 'Item 3', value: 22000, percentage: 16.7 },
|
|
@@ -50,7 +50,7 @@ describe( 'useChartLegendItems', () => {
|
|
|
50
50
|
} );
|
|
51
51
|
|
|
52
52
|
test( 'uses valueDisplay when provided, falling back to formatted value', () => {
|
|
53
|
-
const dataWithDisplay:
|
|
53
|
+
const dataWithDisplay: DataPointPercentageCalculated[] = [
|
|
54
54
|
{ label: 'Item 1', value: 80000, percentage: 60, valueDisplay: 'Custom 80K' },
|
|
55
55
|
{ label: 'Item 2', value: 30000, percentage: 30 },
|
|
56
56
|
];
|
|
@@ -194,7 +194,9 @@ describe( 'useChartLegendItems', () => {
|
|
|
194
194
|
} );
|
|
195
195
|
|
|
196
196
|
test( 'handles data with zero values', () => {
|
|
197
|
-
const zeroData:
|
|
197
|
+
const zeroData: DataPointPercentageCalculated[] = [
|
|
198
|
+
{ label: 'Zero Value', value: 0, percentage: 0 },
|
|
199
|
+
];
|
|
198
200
|
|
|
199
201
|
const { result } = renderHook(
|
|
200
202
|
() =>
|
|
@@ -209,7 +211,7 @@ describe( 'useChartLegendItems', () => {
|
|
|
209
211
|
} );
|
|
210
212
|
|
|
211
213
|
test( 'handles very large numbers', () => {
|
|
212
|
-
const largeData:
|
|
214
|
+
const largeData: DataPointPercentageCalculated[] = [
|
|
213
215
|
{ label: 'Large', value: 1234567890, percentage: 100 },
|
|
214
216
|
];
|
|
215
217
|
|
|
@@ -227,7 +229,7 @@ describe( 'useChartLegendItems', () => {
|
|
|
227
229
|
} );
|
|
228
230
|
|
|
229
231
|
test( 'handles decimal values', () => {
|
|
230
|
-
const decimalData:
|
|
232
|
+
const decimalData: DataPointPercentageCalculated[] = [
|
|
231
233
|
{ label: 'Decimal', value: 1234.5678, percentage: 100 },
|
|
232
234
|
];
|
|
233
235
|
|
|
@@ -247,7 +249,9 @@ describe( 'useChartLegendItems', () => {
|
|
|
247
249
|
|
|
248
250
|
describe( 'Label and Color', () => {
|
|
249
251
|
test( 'preserves label and color from data', () => {
|
|
250
|
-
const data:
|
|
252
|
+
const data: DataPointPercentageCalculated[] = [
|
|
253
|
+
{ label: 'Test Label', value: 100, percentage: 100 },
|
|
254
|
+
];
|
|
251
255
|
|
|
252
256
|
const { result } = renderHook(
|
|
253
257
|
() =>
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type ElementStyles,
|
|
7
7
|
} from '../../../providers';
|
|
8
8
|
import { formatPercentage } from '../../../utils';
|
|
9
|
-
import type { SeriesData, DataPointDate,
|
|
9
|
+
import type { SeriesData, DataPointDate, DataPointPercentageCalculated } from '../../../types';
|
|
10
10
|
import type { BaseLegendItem } from '../types';
|
|
11
11
|
import type { LegendShape } from '@visx/legend/lib/types';
|
|
12
12
|
import type { GlyphProps } from '@visx/xychart';
|
|
@@ -31,7 +31,7 @@ export interface ChartLegendOptions {
|
|
|
31
31
|
* @return Formatted value string
|
|
32
32
|
*/
|
|
33
33
|
function formatPointValue(
|
|
34
|
-
point: DataPointDate |
|
|
34
|
+
point: DataPointDate | DataPointPercentageCalculated,
|
|
35
35
|
showValues: boolean,
|
|
36
36
|
legendValueDisplay: LegendValueDisplay = 'percentage'
|
|
37
37
|
): string {
|
|
@@ -39,16 +39,15 @@ function formatPointValue(
|
|
|
39
39
|
return '';
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// Handle
|
|
42
|
+
// Handle DataPointPercentageCalculated (pie chart data with calculated percentage)
|
|
43
43
|
if ( 'percentage' in point ) {
|
|
44
|
-
const percentagePoint = point as DataPointPercentage;
|
|
45
44
|
switch ( legendValueDisplay ) {
|
|
46
45
|
case 'percentage':
|
|
47
|
-
return formatPercentage(
|
|
46
|
+
return formatPercentage( point.percentage );
|
|
48
47
|
case 'value':
|
|
49
|
-
return formatNumber(
|
|
48
|
+
return formatNumber( point.value );
|
|
50
49
|
case 'valueDisplay':
|
|
51
|
-
return
|
|
50
|
+
return point.valueDisplay || formatNumber( point.value );
|
|
52
51
|
default:
|
|
53
52
|
return '';
|
|
54
53
|
}
|
|
@@ -145,7 +144,7 @@ function processSeriesData(
|
|
|
145
144
|
* @return Array of processed legend items
|
|
146
145
|
*/
|
|
147
146
|
function processPointData(
|
|
148
|
-
pointData: ( DataPointDate |
|
|
147
|
+
pointData: ( DataPointDate | DataPointPercentageCalculated )[],
|
|
149
148
|
getElementStyles: ( params: GetElementStylesParams ) => ElementStyles,
|
|
150
149
|
showValues: boolean,
|
|
151
150
|
legendValueDisplay: LegendValueDisplay,
|
|
@@ -154,9 +153,9 @@ function processPointData(
|
|
|
154
153
|
renderGlyph?: < Datum extends object >( props: GlyphProps< Datum > ) => ReactNode,
|
|
155
154
|
legendShape?: LegendShape< SeriesData[], number >
|
|
156
155
|
): BaseLegendItem[] {
|
|
157
|
-
const mapper = ( point: DataPointDate |
|
|
156
|
+
const mapper = ( point: DataPointDate | DataPointPercentageCalculated, index: number ) => {
|
|
158
157
|
const { color, glyph, shapeStyles } = getElementStyles( {
|
|
159
|
-
data: point as
|
|
158
|
+
data: point as DataPointPercentageCalculated,
|
|
160
159
|
index,
|
|
161
160
|
legendShape,
|
|
162
161
|
} );
|
|
@@ -182,7 +181,7 @@ function processPointData(
|
|
|
182
181
|
* @return Array of legend items ready for display
|
|
183
182
|
*/
|
|
184
183
|
export function useChartLegendItems<
|
|
185
|
-
T extends SeriesData[] | DataPointDate[] |
|
|
184
|
+
T extends SeriesData[] | DataPointDate[] | DataPointPercentageCalculated[],
|
|
186
185
|
>(
|
|
187
186
|
data: T,
|
|
188
187
|
options: ChartLegendOptions = {},
|
|
@@ -215,9 +214,9 @@ export function useChartLegendItems<
|
|
|
215
214
|
);
|
|
216
215
|
}
|
|
217
216
|
|
|
218
|
-
// Handle DataPointDate or
|
|
217
|
+
// Handle DataPointDate or DataPointPercentageCalculated (single data points)
|
|
219
218
|
return processPointData(
|
|
220
|
-
data as ( DataPointDate |
|
|
219
|
+
data as ( DataPointDate | DataPointPercentageCalculated )[],
|
|
221
220
|
getElementStyles,
|
|
222
221
|
showValues,
|
|
223
222
|
legendValueDisplay,
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
export { Legend } from './legend';
|
|
2
2
|
export { useChartLegendItems } from './hooks/use-chart-legend-items';
|
|
3
|
-
export type {
|
|
4
|
-
LegendProps,
|
|
5
|
-
BaseLegendProps,
|
|
6
|
-
BaseLegendItem,
|
|
7
|
-
LegendItemStyles,
|
|
8
|
-
LegendLabelStyles,
|
|
9
|
-
LegendShapeStyles,
|
|
10
|
-
} from './types';
|
|
3
|
+
export type { LegendProps, BaseLegendProps, BaseLegendItem } from './types';
|
|
11
4
|
export type { ChartLegendOptions, LegendValueDisplay } from './hooks/use-chart-legend-items';
|
|
@@ -3,9 +3,21 @@ import { SingleChartContext } from '../../charts/private/single-chart-context';
|
|
|
3
3
|
import { GlobalChartsContext } from '../../providers';
|
|
4
4
|
import { BaseLegend } from './private';
|
|
5
5
|
import type { LegendProps } from './types';
|
|
6
|
+
import type { ChartType } from '../../types';
|
|
7
|
+
import type { LegendShape } from '@visx/legend/lib/types';
|
|
8
|
+
|
|
9
|
+
const defaultShapeByChartType: Partial<
|
|
10
|
+
Record< ChartType, Extract< LegendShape< unknown, unknown >, string > >
|
|
11
|
+
> = {
|
|
12
|
+
line: 'line',
|
|
13
|
+
bar: 'rect',
|
|
14
|
+
pie: 'circle',
|
|
15
|
+
'pie-semi-circle': 'circle',
|
|
16
|
+
leaderboard: 'circle',
|
|
17
|
+
};
|
|
6
18
|
|
|
7
19
|
export const Legend = forwardRef< HTMLDivElement, LegendProps >(
|
|
8
|
-
( { chartId, items, ...props }, ref ) => {
|
|
20
|
+
( { chartId, items, shape, ...props }, ref ) => {
|
|
9
21
|
// Get context but don't throw if it doesn't exist
|
|
10
22
|
const context = useContext( GlobalChartsContext );
|
|
11
23
|
const singleChartContext = useContext( SingleChartContext );
|
|
@@ -14,12 +26,17 @@ export const Legend = forwardRef< HTMLDivElement, LegendProps >(
|
|
|
14
26
|
// When chartId is not provided, we use the context's chartId, meaning it is in a single chart context
|
|
15
27
|
const contextChartId = chartId ?? singleChartContext?.chartId;
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
const chartData = useMemo(
|
|
30
|
+
() => ( contextChartId && context ? context.getChartData( contextChartId ) : undefined ),
|
|
31
|
+
[ contextChartId, context ]
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const contextItems = chartData?.legendItems;
|
|
35
|
+
|
|
36
|
+
// Derive the default legend shape from the chart type when no explicit shape is provided
|
|
37
|
+
const resolvedShape =
|
|
38
|
+
shape ??
|
|
39
|
+
( chartData?.chartType ? defaultShapeByChartType[ chartData.chartType ] : undefined );
|
|
23
40
|
|
|
24
41
|
// Provided items take precedence over context items
|
|
25
42
|
const legendItems = ( items || contextItems ) as typeof items;
|
|
@@ -28,6 +45,14 @@ export const Legend = forwardRef< HTMLDivElement, LegendProps >(
|
|
|
28
45
|
return null;
|
|
29
46
|
}
|
|
30
47
|
|
|
31
|
-
return
|
|
48
|
+
return (
|
|
49
|
+
<BaseLegend
|
|
50
|
+
ref={ ref }
|
|
51
|
+
items={ legendItems }
|
|
52
|
+
shape={ resolvedShape }
|
|
53
|
+
{ ...props }
|
|
54
|
+
chartId={ contextChartId }
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
32
57
|
}
|
|
33
58
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.legend {
|
|
2
|
+
align-self: stretch;
|
|
2
3
|
|
|
3
4
|
&--horizontal {
|
|
4
5
|
display: flex;
|
|
@@ -11,52 +12,33 @@
|
|
|
11
12
|
display: flex;
|
|
12
13
|
flex-direction: column;
|
|
13
14
|
gap: 8px;
|
|
14
|
-
|
|
15
|
-
&.legend--alignment-start {
|
|
16
|
-
align-items: flex-start;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
&.legend--alignment-center {
|
|
20
|
-
align-items: center;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
&.legend--alignment-end {
|
|
24
|
-
align-items: flex-end;
|
|
25
|
-
}
|
|
26
15
|
}
|
|
27
16
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
&.legend--alignment-start {
|
|
33
|
-
justify-content: flex-start;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
&.legend--alignment-center {
|
|
37
|
-
justify-content: center;
|
|
38
|
-
}
|
|
17
|
+
&--alignment-start {
|
|
18
|
+
justify-content: flex-start;
|
|
19
|
+
}
|
|
39
20
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
21
|
+
&--alignment-center {
|
|
22
|
+
justify-content: center;
|
|
43
23
|
}
|
|
44
24
|
|
|
45
|
-
&--
|
|
46
|
-
|
|
25
|
+
&--alignment-end {
|
|
26
|
+
justify-content: flex-end;
|
|
27
|
+
}
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
29
|
+
// Vertical legends align on the cross-axis instead
|
|
30
|
+
&--vertical.legend--alignment-start {
|
|
31
|
+
align-items: flex-start;
|
|
32
|
+
}
|
|
51
33
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
&--vertical.legend--alignment-center {
|
|
35
|
+
align-items: center;
|
|
36
|
+
}
|
|
55
37
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
38
|
+
&--vertical.legend--alignment-end {
|
|
39
|
+
align-items: flex-end;
|
|
59
40
|
}
|
|
41
|
+
|
|
60
42
|
}
|
|
61
43
|
|
|
62
44
|
.legend-item {
|
|
@@ -67,7 +67,6 @@ export const BaseLegend: ForwardRefExoticComponent<
|
|
|
67
67
|
items,
|
|
68
68
|
className,
|
|
69
69
|
orientation = 'horizontal',
|
|
70
|
-
position = 'bottom',
|
|
71
70
|
alignment = 'center',
|
|
72
71
|
shape = 'rect',
|
|
73
72
|
fill = valueOrIdentityString,
|
|
@@ -177,7 +176,6 @@ export const BaseLegend: ForwardRefExoticComponent<
|
|
|
177
176
|
styles.legend,
|
|
178
177
|
styles[ `legend--${ orientation }` ],
|
|
179
178
|
styles[ `legend--alignment-${ alignment }` ],
|
|
180
|
-
styles[ `legend--position-${ position }` ],
|
|
181
179
|
className
|
|
182
180
|
) }
|
|
183
181
|
style={ {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/* eslint-disable react/jsx-no-bind */
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
|
-
import {
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { SingleChartContext } from '../../../charts/private/single-chart-context';
|
|
6
|
+
import { GlobalChartsProvider, useChartId, useChartRegistration } from '../../../providers';
|
|
7
|
+
import { Legend } from '../legend';
|
|
5
8
|
import { BaseLegend } from '../private/base-legend';
|
|
9
|
+
import type { ChartType } from '../../../types';
|
|
6
10
|
import type { LegendProps } from '../types';
|
|
7
11
|
|
|
8
12
|
const TestShape: LegendProps[ 'shape' ] = props => {
|
|
@@ -425,6 +429,94 @@ describe( 'BaseLegend', () => {
|
|
|
425
429
|
} );
|
|
426
430
|
} );
|
|
427
431
|
|
|
432
|
+
describe( 'Legend shape defaults from chart type', () => {
|
|
433
|
+
const legendItems = [
|
|
434
|
+
{ label: 'Series 1', color: '#ff0000' },
|
|
435
|
+
{ label: 'Series 2', color: '#00ff00' },
|
|
436
|
+
];
|
|
437
|
+
|
|
438
|
+
const CustomShape: LegendProps[ 'shape' ] = props => (
|
|
439
|
+
<span data-testid="custom-shape" style={ { color: props.fill as string } } />
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const ChartRegistrar = ( {
|
|
443
|
+
chartType,
|
|
444
|
+
chartId,
|
|
445
|
+
}: {
|
|
446
|
+
chartType: ChartType;
|
|
447
|
+
chartId: string;
|
|
448
|
+
} ) => {
|
|
449
|
+
const resolvedId = useChartId( chartId );
|
|
450
|
+
const metadata = useMemo( () => ( {} ), [] );
|
|
451
|
+
useChartRegistration( {
|
|
452
|
+
chartId: resolvedId,
|
|
453
|
+
legendItems,
|
|
454
|
+
chartType,
|
|
455
|
+
isDataValid: true,
|
|
456
|
+
metadata,
|
|
457
|
+
} );
|
|
458
|
+
return null;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const renderLegendWithChartType = (
|
|
462
|
+
chartType: ChartType,
|
|
463
|
+
explicitShape?: LegendProps[ 'shape' ]
|
|
464
|
+
) => {
|
|
465
|
+
const chartId = `test-${ chartType }`;
|
|
466
|
+
|
|
467
|
+
return render(
|
|
468
|
+
<GlobalChartsProvider>
|
|
469
|
+
<ChartRegistrar chartType={ chartType } chartId={ chartId } />
|
|
470
|
+
<SingleChartContext.Provider value={ { chartId } }>
|
|
471
|
+
<Legend shape={ explicitShape } />
|
|
472
|
+
</SingleChartContext.Provider>
|
|
473
|
+
</GlobalChartsProvider>
|
|
474
|
+
);
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
it( 'uses line shape for line chart type', () => {
|
|
478
|
+
renderLegendWithChartType( 'line' );
|
|
479
|
+
expect( screen.getByRole( 'list' ) ).toBeInTheDocument();
|
|
480
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
481
|
+
|
|
482
|
+
const html = document.body.innerHTML;
|
|
483
|
+
expect( html ).toContain( '<line' );
|
|
484
|
+
} );
|
|
485
|
+
|
|
486
|
+
it( 'uses rect shape for bar chart type', () => {
|
|
487
|
+
renderLegendWithChartType( 'bar' );
|
|
488
|
+
expect( screen.getByRole( 'list' ) ).toBeInTheDocument();
|
|
489
|
+
|
|
490
|
+
// visx ShapeRect renders a <div> with inline background style inside
|
|
491
|
+
// .visx-legend-shape. No testids or roles on these elements, so direct
|
|
492
|
+
// node access is necessary.
|
|
493
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
494
|
+
const shapes = document.querySelectorAll( '.visx-legend-shape > div' );
|
|
495
|
+
expect( shapes ).toHaveLength( 2 );
|
|
496
|
+
shapes.forEach( shape => {
|
|
497
|
+
expect( ( shape as HTMLElement ).style.background ).toBeTruthy();
|
|
498
|
+
} );
|
|
499
|
+
} );
|
|
500
|
+
|
|
501
|
+
it( 'uses circle shape for pie chart type', () => {
|
|
502
|
+
renderLegendWithChartType( 'pie' );
|
|
503
|
+
expect( screen.getByRole( 'list' ) ).toBeInTheDocument();
|
|
504
|
+
|
|
505
|
+
const html = document.body.innerHTML;
|
|
506
|
+
expect( html ).toContain( '<circle' );
|
|
507
|
+
expect( html ).not.toContain( '<line' );
|
|
508
|
+
} );
|
|
509
|
+
|
|
510
|
+
it( 'allows explicit shape to override chart type default', () => {
|
|
511
|
+
renderLegendWithChartType( 'line', CustomShape );
|
|
512
|
+
expect( screen.getByRole( 'list' ) ).toBeInTheDocument();
|
|
513
|
+
expect( screen.getAllByTestId( 'custom-shape' ) ).toHaveLength( 2 );
|
|
514
|
+
|
|
515
|
+
const html = document.body.innerHTML;
|
|
516
|
+
expect( html ).not.toContain( '<line' );
|
|
517
|
+
} );
|
|
518
|
+
} );
|
|
519
|
+
|
|
428
520
|
describe( 'Interactive legend', () => {
|
|
429
521
|
it( 'renders interactive legend items with proper attributes', () => {
|
|
430
522
|
render(
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { LegendOrdinal } from '@visx/legend';
|
|
2
|
+
import type {
|
|
3
|
+
LegendItemStyles,
|
|
4
|
+
LegendLabelStyles,
|
|
5
|
+
LegendPosition,
|
|
6
|
+
LegendShapeStyles,
|
|
7
|
+
} from '../../types';
|
|
2
8
|
import type { GlyphProps, LineStyles } from '@visx/xychart';
|
|
3
9
|
import type { ComponentProps, CSSProperties, ReactNode } from 'react';
|
|
4
10
|
|
|
@@ -7,43 +13,10 @@ type VisxLegendProps = Pick<
|
|
|
7
13
|
'className' | 'shape' | 'fill' | 'size' | 'labelFormat' | 'labelTransform'
|
|
8
14
|
>;
|
|
9
15
|
|
|
10
|
-
export type LegendItemStyles = {
|
|
11
|
-
/** Margin around each legend item. */
|
|
12
|
-
margin?: CSSProperties[ 'margin' ];
|
|
13
|
-
/** Flex direction for items within each legend entry. */
|
|
14
|
-
flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type LegendLabelStyles = Pick< CSSProperties, 'justifyContent' | 'flex' | 'margin' > & {
|
|
18
|
-
/**
|
|
19
|
-
* Maximum width for legend label text as a CSS value (e.g. '200px', '50%', '10rem').
|
|
20
|
-
* When set, text overflow behavior is controlled by textOverflow.
|
|
21
|
-
*/
|
|
22
|
-
maxWidth?: string;
|
|
23
|
-
/**
|
|
24
|
-
* Controls how text behaves when it exceeds maxWidth.
|
|
25
|
-
* - 'ellipsis': Truncate with ellipsis (ideal for widgets/small devices)
|
|
26
|
-
* - 'wrap': Wrap text to multiple lines (default, ideal for larger displays)
|
|
27
|
-
*/
|
|
28
|
-
textOverflow?: 'ellipsis' | 'wrap';
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type LegendShapeStyles = {
|
|
32
|
-
/** Width of the legend shape in pixels. */
|
|
33
|
-
width?: number;
|
|
34
|
-
/** Height of the legend shape in pixels. */
|
|
35
|
-
height?: number;
|
|
36
|
-
/** Margin around the legend shape. */
|
|
37
|
-
margin?: CSSProperties[ 'margin' ];
|
|
38
|
-
};
|
|
39
|
-
|
|
40
16
|
export type BaseLegendProps = VisxLegendProps & {
|
|
41
17
|
items: BaseLegendItem[];
|
|
42
18
|
orientation?: 'horizontal' | 'vertical';
|
|
43
|
-
|
|
44
|
-
* TODO: Add 'left' | 'right' positioning support in future implementation
|
|
45
|
-
*/
|
|
46
|
-
position?: 'top' | 'bottom';
|
|
19
|
+
position?: LegendPosition;
|
|
47
20
|
alignment?: 'start' | 'center' | 'end';
|
|
48
21
|
/** Additional CSS class name for legend items. */
|
|
49
22
|
itemClassName?: string;
|
package/src/hooks/index.ts
CHANGED
|
@@ -4,9 +4,9 @@ export { useXYChartTheme } from './use-xychart-theme';
|
|
|
4
4
|
export { useChartDataTransform } from './use-chart-data-transform';
|
|
5
5
|
export { useChartMargin } from './use-chart-margin';
|
|
6
6
|
export { useElementSize } from './use-element-size';
|
|
7
|
-
export { useHasLegendChild } from './use-has-legend-child';
|
|
8
7
|
export { useTextTruncation } from './use-text-truncation';
|
|
9
8
|
export { useZeroValueDisplay } from './use-zero-value-display';
|
|
9
|
+
export { useDataWithPercentages } from './use-data-with-percentages';
|
|
10
10
|
export { useInteractiveLegendData } from './use-interactive-legend-data';
|
|
11
11
|
export { usePrefersReducedMotion } from './use-prefers-reduced-motion';
|
|
12
12
|
export { useTooltipPortalRelocator } from './use-tooltip-portal-relocator';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
interface DataPointWithValue {
|
|
4
|
+
value: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook to calculate percentages from values for chart data.
|
|
9
|
+
* Ensures percentages are always derived from values (single source of truth).
|
|
10
|
+
*
|
|
11
|
+
* @param data - Array of data points with values
|
|
12
|
+
* @return Data with calculated percentages
|
|
13
|
+
*/
|
|
14
|
+
export const useDataWithPercentages = < T extends DataPointWithValue >(
|
|
15
|
+
data: T[]
|
|
16
|
+
): ( T & { percentage: number } )[] => {
|
|
17
|
+
return useMemo( () => {
|
|
18
|
+
const totalValue = data.reduce( ( sum, segment ) => sum + segment.value, 0 );
|
|
19
|
+
return data.map( segment => ( {
|
|
20
|
+
...segment,
|
|
21
|
+
percentage: totalValue > 0 ? ( segment.value / totalValue ) * 100 : 0,
|
|
22
|
+
} ) );
|
|
23
|
+
}, [ data ] );
|
|
24
|
+
};
|
|
@@ -2,7 +2,8 @@ import { useMemo } from 'react';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Data point interface for charts with interactive legends.
|
|
5
|
-
* Requires label for series identification, value for calculations,
|
|
5
|
+
* Requires label for series identification, value for calculations,
|
|
6
|
+
* and percentage (should be pre-calculated by the chart component).
|
|
6
7
|
*/
|
|
7
8
|
interface DataPointWithPercentage {
|
|
8
9
|
label: string;
|
|
@@ -14,7 +15,7 @@ interface DataPointWithPercentage {
|
|
|
14
15
|
* Parameters for the useInteractiveLegendData hook.
|
|
15
16
|
*/
|
|
16
17
|
interface UseInteractiveLegendDataParams< T extends DataPointWithPercentage > {
|
|
17
|
-
/** The chart data
|
|
18
|
+
/** The chart data with pre-calculated percentages */
|
|
18
19
|
data: T[];
|
|
19
20
|
/** Unique chart identifier, required for interactive legends */
|
|
20
21
|
chartId: string | undefined;
|
|
@@ -33,9 +34,9 @@ interface UseInteractiveLegendDataResult< T extends DataPointWithPercentage > {
|
|
|
33
34
|
/** Boolean indicating if all segments are hidden */
|
|
34
35
|
allSegmentsHidden: boolean;
|
|
35
36
|
/**
|
|
36
|
-
* Legend data with
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* Legend data with stable percentage formatting.
|
|
38
|
+
* Hidden items keep their original percentage.
|
|
39
|
+
* Visible items show recalculated percentages that total 100%.
|
|
39
40
|
*/
|
|
40
41
|
legendData: T[];
|
|
41
42
|
}
|
|
@@ -85,8 +86,9 @@ export const useInteractiveLegendData = < T extends DataPointWithPercentage >( {
|
|
|
85
86
|
isSeriesVisible,
|
|
86
87
|
}: UseInteractiveLegendDataParams< T > ): UseInteractiveLegendDataResult< T > => {
|
|
87
88
|
// Filter and recalculate data for interactive legends
|
|
89
|
+
// Note: data should already have percentages calculated by the chart component
|
|
88
90
|
const visibleData = useMemo( () => {
|
|
89
|
-
// If interactive mode is disabled or no chartId, return
|
|
91
|
+
// If interactive mode is disabled or no chartId, return data as-is
|
|
90
92
|
if ( ! chartId || ! legendInteractive ) {
|
|
91
93
|
return data;
|
|
92
94
|
}
|
|
@@ -99,9 +101,8 @@ export const useInteractiveLegendData = < T extends DataPointWithPercentage >( {
|
|
|
99
101
|
return [];
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
// Recalculate percentages so visible segments total 100%
|
|
104
|
+
// Recalculate percentages from values so visible segments total 100%
|
|
103
105
|
const totalValue = filtered.reduce( ( sum, segment ) => sum + segment.value, 0 );
|
|
104
|
-
|
|
105
106
|
return filtered.map( segment => ( {
|
|
106
107
|
...segment,
|
|
107
108
|
percentage: totalValue > 0 ? ( segment.value / totalValue ) * 100 : 0,
|
|
@@ -113,24 +114,26 @@ export const useInteractiveLegendData = < T extends DataPointWithPercentage >( {
|
|
|
113
114
|
return legendInteractive && visibleData.length === 0;
|
|
114
115
|
}, [ legendInteractive, visibleData ] );
|
|
115
116
|
|
|
116
|
-
// Prepare legend data with
|
|
117
|
-
//
|
|
117
|
+
// Prepare legend data with percentages
|
|
118
|
+
// Hidden items keep their original percentage (calculated from all values)
|
|
119
|
+
// Visible items show recalculated percentages (totaling 100%)
|
|
118
120
|
const legendData = useMemo( () => {
|
|
119
121
|
if ( ! legendInteractive || ! chartId ) {
|
|
120
122
|
return data;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
// Map
|
|
125
|
+
// Build a Map for O(1) lookups instead of O(n) find() calls
|
|
126
|
+
const visibleDataMap = new Map( visibleData.map( d => [ d.label, d ] ) );
|
|
127
|
+
|
|
124
128
|
return data.map( segment => {
|
|
125
129
|
const isVisible = isSeriesVisible( chartId, segment.label );
|
|
126
130
|
if ( ! isVisible ) {
|
|
127
|
-
//
|
|
131
|
+
// Hidden items keep original percentage
|
|
128
132
|
return segment;
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
// For visible items,
|
|
132
|
-
|
|
133
|
-
return recalculated || segment;
|
|
135
|
+
// For visible items, get the recalculated percentage from visibleData
|
|
136
|
+
return visibleDataMap.get( segment.label ) || segment;
|
|
134
137
|
} );
|
|
135
138
|
}, [ data, visibleData, legendInteractive, chartId, isSeriesVisible ] );
|
|
136
139
|
|