@automattic/charts 0.56.7 → 0.57.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/AGENTS.md +28 -98
- package/CHANGELOG.md +16 -0
- package/dist/charts/bar-chart/index.cjs +5 -6
- package/dist/charts/bar-chart/index.cjs.map +1 -1
- package/dist/charts/bar-chart/index.d.cts +3 -3
- package/dist/charts/bar-chart/index.d.ts +3 -3
- package/dist/charts/bar-chart/index.js +4 -5
- package/dist/charts/bar-list-chart/index.cjs +6 -7
- package/dist/charts/bar-list-chart/index.cjs.map +1 -1
- package/dist/charts/bar-list-chart/index.d.cts +3 -3
- package/dist/charts/bar-list-chart/index.d.ts +3 -3
- package/dist/charts/bar-list-chart/index.js +5 -6
- package/dist/charts/conversion-funnel-chart/index.cjs +5 -6
- package/dist/charts/conversion-funnel-chart/index.cjs.map +1 -1
- package/dist/charts/conversion-funnel-chart/index.d.cts +1 -1
- package/dist/charts/conversion-funnel-chart/index.d.ts +1 -1
- package/dist/charts/conversion-funnel-chart/index.js +4 -5
- package/dist/charts/geo-chart/index.cjs +4 -4
- package/dist/charts/geo-chart/index.d.cts +1 -1
- package/dist/charts/geo-chart/index.d.ts +1 -1
- package/dist/charts/geo-chart/index.js +3 -3
- package/dist/charts/leaderboard-chart/index.cjs +5 -5
- package/dist/charts/leaderboard-chart/index.css +8 -9
- package/dist/charts/leaderboard-chart/index.css.map +1 -1
- package/dist/charts/leaderboard-chart/index.d.cts +3 -3
- package/dist/charts/leaderboard-chart/index.d.ts +3 -3
- package/dist/charts/leaderboard-chart/index.js +4 -4
- package/dist/charts/line-chart/index.cjs +5 -6
- package/dist/charts/line-chart/index.cjs.map +1 -1
- package/dist/charts/line-chart/index.d.cts +3 -3
- package/dist/charts/line-chart/index.d.ts +3 -3
- package/dist/charts/line-chart/index.js +4 -5
- package/dist/charts/pie-chart/index.cjs +5 -6
- package/dist/charts/pie-chart/index.cjs.map +1 -1
- package/dist/charts/pie-chart/index.d.cts +4 -4
- package/dist/charts/pie-chart/index.d.ts +4 -4
- package/dist/charts/pie-chart/index.js +4 -5
- package/dist/charts/pie-semi-circle-chart/index.cjs +5 -6
- package/dist/charts/pie-semi-circle-chart/index.cjs.map +1 -1
- package/dist/charts/pie-semi-circle-chart/index.d.cts +4 -4
- package/dist/charts/pie-semi-circle-chart/index.d.ts +4 -4
- package/dist/charts/pie-semi-circle-chart/index.js +4 -5
- package/dist/charts/sparkline/index.cjs +6 -7
- package/dist/charts/sparkline/index.cjs.map +1 -1
- package/dist/charts/sparkline/index.js +5 -6
- package/dist/{chunk-XD2HV7M5.js → chunk-2NCY7R4G.js} +127 -762
- package/dist/chunk-2NCY7R4G.js.map +1 -0
- package/dist/{chunk-RFSHE3HL.js → chunk-32DH6JDF.js} +64 -43
- package/dist/chunk-32DH6JDF.js.map +1 -0
- package/dist/{chunk-SSFFCBCF.js → chunk-4OPFE4RM.js} +11 -8
- package/dist/chunk-4OPFE4RM.js.map +1 -0
- package/dist/{chunk-CAFJRZPZ.cjs → chunk-77OKCVQN.cjs} +17 -17
- package/dist/{chunk-CAFJRZPZ.cjs.map → chunk-77OKCVQN.cjs.map} +1 -1
- package/dist/{chunk-K6TGILHX.cjs → chunk-7FQX4ALL.cjs} +6 -6
- package/dist/{chunk-K6TGILHX.cjs.map → chunk-7FQX4ALL.cjs.map} +1 -1
- package/dist/{chunk-7FDQGBY7.js → chunk-BCX5THDQ.js} +9 -7
- package/dist/chunk-BCX5THDQ.js.map +1 -0
- package/dist/{chunk-KHQPN77E.js → chunk-CZGYJKG6.js} +4 -4
- package/dist/{chunk-3EXJP67N.cjs → chunk-D2UH4CFE.cjs} +9 -9
- package/dist/{chunk-3EXJP67N.cjs.map → chunk-D2UH4CFE.cjs.map} +1 -1
- package/dist/{chunk-TE63Y5PX.js → chunk-DAU3HNEG.js} +2 -2
- package/dist/chunk-DAU3HNEG.js.map +1 -0
- package/dist/{chunk-MDRCAGKZ.js → chunk-H2V4JMSA.js} +3 -3
- package/dist/{chunk-UFRBUT2D.cjs → chunk-I35UYJJR.cjs} +49 -6
- package/dist/chunk-I35UYJJR.cjs.map +1 -0
- package/dist/{chunk-GWBS65VC.js → chunk-IU4DYUAV.js} +3 -3
- package/dist/{chunk-E62LCBGD.js → chunk-PXLEMUGJ.js} +3 -3
- package/dist/{chunk-YDVHT7GS.cjs → chunk-RHHVEJHJ.cjs} +83 -62
- package/dist/chunk-RHHVEJHJ.cjs.map +1 -0
- package/dist/{chunk-YAXY5L7I.cjs → chunk-TO3OQBXG.cjs} +5 -5
- package/dist/{chunk-YAXY5L7I.cjs.map → chunk-TO3OQBXG.cjs.map} +1 -1
- package/dist/{chunk-VPAEBI2F.js → chunk-V36ERY7Y.js} +9 -7
- package/dist/chunk-V36ERY7Y.js.map +1 -0
- package/dist/{chunk-X7JL2NYJ.cjs → chunk-VJM5XCB4.cjs} +33 -30
- package/dist/chunk-VJM5XCB4.cjs.map +1 -0
- package/dist/{chunk-ZVGEDXDP.cjs → chunk-VTS3PNMS.cjs} +2 -2
- package/dist/{chunk-ZVGEDXDP.cjs.map → chunk-VTS3PNMS.cjs.map} +1 -1
- package/dist/{chunk-OMS5QIJN.js → chunk-WLODYNLB.js} +9 -7
- package/dist/chunk-WLODYNLB.js.map +1 -0
- package/dist/{chunk-NQJE2CC7.cjs → chunk-XKRJL2QT.cjs} +25 -23
- package/dist/chunk-XKRJL2QT.cjs.map +1 -0
- package/dist/{chunk-O2JIANHK.cjs → chunk-YE2T52VZ.cjs} +33 -31
- package/dist/chunk-YE2T52VZ.cjs.map +1 -0
- package/dist/{chunk-IS5YYLTV.js → chunk-Z26M4V2M.js} +46 -3
- package/dist/chunk-Z26M4V2M.js.map +1 -0
- package/dist/{chunk-55ZCOYDF.cjs → chunk-Z45KX47P.cjs} +153 -788
- package/dist/chunk-Z45KX47P.cjs.map +1 -0
- package/dist/{chunk-BXFD7JIG.cjs → chunk-ZH4F5RMG.cjs} +26 -24
- package/dist/chunk-ZH4F5RMG.cjs.map +1 -0
- package/dist/components/legend/index.cjs +3 -3
- package/dist/components/legend/index.d.cts +4 -4
- package/dist/components/legend/index.d.ts +4 -4
- package/dist/components/legend/index.js +2 -2
- package/dist/components/tooltip/index.d.cts +1 -1
- package/dist/components/tooltip/index.d.ts +1 -1
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.d.cts +7 -3
- package/dist/hooks/index.d.ts +7 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +13 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +8 -9
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +12 -13
- package/dist/{leaderboard-chart-BSgEw_Um.d.ts → leaderboard-chart-BKYYXcg2.d.ts} +5 -9
- package/dist/{leaderboard-chart-COtgamhe.d.cts → leaderboard-chart-DR7CGb0L.d.cts} +5 -9
- package/dist/{legend-C9ahiwOt.d.cts → legend-C2grwnWk.d.cts} +1 -1
- package/dist/{legend-jjMmhSg3.d.ts → legend-Cj0xM5dU.d.ts} +1 -1
- package/dist/providers/index.cjs +3 -3
- package/dist/providers/index.d.cts +3 -3
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.js +2 -2
- package/dist/{themes-DQzmaSze.d.ts → themes-BmVGrYnF.d.ts} +2 -2
- package/dist/{themes-CVR5rmIs.d.cts → themes-CyjKm-P_.d.cts} +2 -2
- package/dist/{types-DQNnq5Fr.d.ts → types-CuUEszrM.d.ts} +1 -1
- package/dist/{types-CzdN7rUe.d.cts → types-DZordNiO.d.cts} +11 -7
- package/dist/{types-CzdN7rUe.d.ts → types-DZordNiO.d.ts} +11 -7
- package/dist/types-I67mddpr.d.cts +78 -0
- package/dist/types-I67mddpr.d.ts +78 -0
- package/dist/{types-BBwg4Evw.d.cts → types-KtOPPzPX.d.cts} +1 -1
- package/dist/utils/index.cjs +2 -2
- package/dist/utils/index.d.cts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +6 -4
- package/src/charts/bar-chart/bar-chart.tsx +4 -3
- package/src/charts/bar-chart/test/bar-chart.test.tsx +30 -0
- package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +2 -2
- package/src/charts/leaderboard-chart/hooks/use-leaderboard-legend-items.ts +0 -2
- package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +9 -10
- package/src/charts/leaderboard-chart/leaderboard-chart.tsx +95 -70
- package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +58 -29
- package/src/charts/leaderboard-chart/test/use-leaderboard-legend-items.test.tsx +2 -5
- package/src/charts/leaderboard-chart/types.ts +4 -7
- package/src/charts/line-chart/line-chart.tsx +2 -3
- package/src/charts/pie-chart/pie-chart.tsx +2 -3
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +2 -3
- package/src/components/legend/index.ts +8 -1
- package/src/components/legend/private/base-legend.tsx +32 -22
- package/src/components/legend/test/legend.test.tsx +148 -52
- package/src/components/legend/types.ts +42 -16
- package/src/hooks/test/use-zero-value-display.test.tsx +206 -0
- package/src/hooks/use-zero-value-display.ts +52 -23
- package/src/index.ts +7 -1
- package/src/providers/chart-context/test/chart-context.test.tsx +12 -6
- package/src/providers/chart-context/themes.ts +6 -4
- package/src/types.ts +11 -7
- package/src/utils/get-styles.ts +1 -1
- package/src/utils/test/get-styles.test.ts +12 -10
- package/dist/chunk-55ZCOYDF.cjs.map +0 -1
- package/dist/chunk-7FDQGBY7.js.map +0 -1
- package/dist/chunk-BXFD7JIG.cjs.map +0 -1
- package/dist/chunk-IS5YYLTV.js.map +0 -1
- package/dist/chunk-KNIMXN6Z.js +0 -51
- package/dist/chunk-KNIMXN6Z.js.map +0 -1
- package/dist/chunk-NQJE2CC7.cjs.map +0 -1
- package/dist/chunk-O2JIANHK.cjs.map +0 -1
- package/dist/chunk-OMS5QIJN.js.map +0 -1
- package/dist/chunk-RFSHE3HL.js.map +0 -1
- package/dist/chunk-SSFFCBCF.js.map +0 -1
- package/dist/chunk-SUDERBUA.cjs +0 -51
- package/dist/chunk-SUDERBUA.cjs.map +0 -1
- package/dist/chunk-TE63Y5PX.js.map +0 -1
- package/dist/chunk-UFRBUT2D.cjs.map +0 -1
- package/dist/chunk-VPAEBI2F.js.map +0 -1
- package/dist/chunk-X7JL2NYJ.cjs.map +0 -1
- package/dist/chunk-XD2HV7M5.js.map +0 -1
- package/dist/chunk-YDVHT7GS.cjs.map +0 -1
- package/dist/types-C05PdDJa.d.cts +0 -57
- package/dist/types-C05PdDJa.d.ts +0 -57
- /package/dist/{chunk-KHQPN77E.js.map → chunk-CZGYJKG6.js.map} +0 -0
- /package/dist/{chunk-MDRCAGKZ.js.map → chunk-H2V4JMSA.js.map} +0 -0
- /package/dist/{chunk-GWBS65VC.js.map → chunk-IU4DYUAV.js.map} +0 -0
- /package/dist/{chunk-E62LCBGD.js.map → chunk-PXLEMUGJ.js.map} +0 -0
|
@@ -562,6 +562,36 @@ describe( 'BarChart', () => {
|
|
|
562
562
|
expect( width ).toBeGreaterThan( 0 );
|
|
563
563
|
} );
|
|
564
564
|
} );
|
|
565
|
+
|
|
566
|
+
test( 'ensures minimum pixel height for zero values in small charts', () => {
|
|
567
|
+
// With a small chart height (100px) and large data range, zero-value bars
|
|
568
|
+
// should still be visible (at least 3px based on MIN_PIXEL_HEIGHT)
|
|
569
|
+
renderWithTheme( {
|
|
570
|
+
showZeroValues: true,
|
|
571
|
+
height: 100,
|
|
572
|
+
data: [
|
|
573
|
+
{
|
|
574
|
+
label: 'Test Series',
|
|
575
|
+
data: [
|
|
576
|
+
{ label: 'Zero', value: 0 },
|
|
577
|
+
{ label: 'Large', value: 10000 },
|
|
578
|
+
],
|
|
579
|
+
options: {},
|
|
580
|
+
},
|
|
581
|
+
],
|
|
582
|
+
} );
|
|
583
|
+
|
|
584
|
+
const svgElement = screen.getByRole( 'grid', { name: /bar chart/i } ).querySelector( 'svg' );
|
|
585
|
+
const bars = svgElement?.querySelectorAll( '.visx-bar-group rect' );
|
|
586
|
+
|
|
587
|
+
expect( bars?.length ).toBe( 2 );
|
|
588
|
+
|
|
589
|
+
// The zero-value bar (first bar) should have a minimum visible height.
|
|
590
|
+
// We check for >= 2px to allow for rounding in the pixel calculation.
|
|
591
|
+
const zeroBar = bars?.[ 0 ];
|
|
592
|
+
const zeroBarHeight = parseFloat( zeroBar?.getAttribute( 'height' ) || '0' );
|
|
593
|
+
expect( zeroBarHeight ).toBeGreaterThanOrEqual( 2 );
|
|
594
|
+
} );
|
|
565
595
|
} );
|
|
566
596
|
|
|
567
597
|
/* eslint-enable testing-library/no-node-access */
|
|
@@ -372,7 +372,7 @@ describe( 'ConversionFunnelChart', () => {
|
|
|
372
372
|
expect( customRenderMainMetric ).toHaveBeenCalledWith( {
|
|
373
373
|
mainRate: 10.3,
|
|
374
374
|
changeIndicator: undefined,
|
|
375
|
-
className:
|
|
375
|
+
className: 'main-metric',
|
|
376
376
|
changeColor: expect.any( String ),
|
|
377
377
|
} );
|
|
378
378
|
} );
|
|
@@ -427,7 +427,7 @@ describe( 'ConversionFunnelChart', () => {
|
|
|
427
427
|
index: 1,
|
|
428
428
|
top: expect.any( Number ),
|
|
429
429
|
left: expect.any( Number ),
|
|
430
|
-
className:
|
|
430
|
+
className: 'tooltip-wrapper',
|
|
431
431
|
} );
|
|
432
432
|
} );
|
|
433
433
|
|
|
@@ -53,7 +53,6 @@ export function useLeaderboardLegendItems( {
|
|
|
53
53
|
|
|
54
54
|
items.push( {
|
|
55
55
|
label: legendLabels?.primary || __( 'Current period', 'jetpack-charts' ),
|
|
56
|
-
value: '',
|
|
57
56
|
color: resolvedPrimaryColor,
|
|
58
57
|
} );
|
|
59
58
|
|
|
@@ -66,7 +65,6 @@ export function useLeaderboardLegendItems( {
|
|
|
66
65
|
|
|
67
66
|
items.push( {
|
|
68
67
|
label: legendLabels?.comparison || __( 'Previous period', 'jetpack-charts' ),
|
|
69
|
-
value: '',
|
|
70
68
|
color: resolvedSecondaryColor,
|
|
71
69
|
} );
|
|
72
70
|
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
.leaderboardChart {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
2
|
transition: opacity 0.3s ease-in-out;
|
|
5
3
|
|
|
6
|
-
&--
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
&--with-legend {
|
|
11
|
-
gap: 16px;
|
|
4
|
+
&--responsive {
|
|
5
|
+
height: 100%;
|
|
6
|
+
width: 100%;
|
|
12
7
|
}
|
|
13
8
|
|
|
14
9
|
&--loading {
|
|
15
10
|
opacity: 0.5;
|
|
16
11
|
}
|
|
12
|
+
|
|
13
|
+
&__content {
|
|
14
|
+
flex: 1;
|
|
15
|
+
min-height: 0;
|
|
16
|
+
overflow: auto;
|
|
17
|
+
}
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
.barWithLabelContainer {
|
|
@@ -61,8 +62,6 @@
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
.valueContainer {
|
|
64
|
-
display: flex;
|
|
65
|
-
gap: 4px;
|
|
66
65
|
justify-content: flex-end;
|
|
67
66
|
}
|
|
68
67
|
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/* eslint-disable @wordpress/no-unsafe-wp-apis */
|
|
2
|
-
import {
|
|
3
|
-
__experimentalVStack as VStack,
|
|
4
|
-
__experimentalGrid as Grid,
|
|
5
|
-
__experimentalText as Text,
|
|
6
|
-
} from '@wordpress/components';
|
|
2
|
+
import { __experimentalGrid as Grid, __experimentalText as Text } from '@wordpress/components';
|
|
7
3
|
import { Fragment } from '@wordpress/element';
|
|
8
4
|
import { __ } from '@wordpress/i18n';
|
|
5
|
+
import { Stack } from '@wordpress/ui';
|
|
9
6
|
import clsx from 'clsx';
|
|
10
7
|
import { useContext, useMemo, type FC } from 'react';
|
|
11
8
|
import { Legend } from '../../components/legend';
|
|
@@ -116,6 +113,8 @@ const BarWithLabel = ( {
|
|
|
116
113
|
* @param props - Component props
|
|
117
114
|
* @param props.data - Array of leaderboard entries to display
|
|
118
115
|
* @param props.chartId - Optional unique identifier for the chart
|
|
116
|
+
* @param props.width - Optional width of the chart container in pixels
|
|
117
|
+
* @param props.height - Optional height of the chart container in pixels
|
|
119
118
|
* @param props.withComparison - Whether to show comparison data
|
|
120
119
|
* @param props.withOverlayLabel - Whether to overlay the label on top of the bar
|
|
121
120
|
* @param props.primaryColor - Primary color for current period bars
|
|
@@ -129,10 +128,10 @@ const BarWithLabel = ( {
|
|
|
129
128
|
* @param props.legendPosition - Legend position
|
|
130
129
|
* @param props.legendAlignment - Legend alignment
|
|
131
130
|
* @param props.legendShape - Legend shape
|
|
132
|
-
* @param props.
|
|
133
|
-
* @param props.legendShapeHeight - Height of legend shapes in pixels
|
|
131
|
+
* @param props.legendShapeStyles - Styles for legend shapes (width, height, margin)
|
|
134
132
|
* @param props.legendLabels - Custom labels for legend items
|
|
135
133
|
* @param props.legendInteractive - Whether legend items are interactive (clickable to toggle series visibility)
|
|
134
|
+
* @param props.gap - Spacing between legend and chart content
|
|
136
135
|
* @param props.children - Child components for composition API
|
|
137
136
|
* @param props.className - Additional CSS class name
|
|
138
137
|
* @param props.style - Custom styling for the chart container
|
|
@@ -141,6 +140,8 @@ const BarWithLabel = ( {
|
|
|
141
140
|
const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
|
|
142
141
|
data,
|
|
143
142
|
chartId: providedChartId,
|
|
143
|
+
width: propWidth,
|
|
144
|
+
height: propHeight,
|
|
144
145
|
withComparison = false,
|
|
145
146
|
withOverlayLabel = false,
|
|
146
147
|
primaryColor,
|
|
@@ -154,16 +155,17 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
|
|
|
154
155
|
legendPosition = 'bottom',
|
|
155
156
|
legendAlignment = 'center',
|
|
156
157
|
legendShape = 'circle',
|
|
157
|
-
|
|
158
|
-
legendShapeHeight = 8,
|
|
158
|
+
legendShapeStyles: legendShapeStylesProp,
|
|
159
159
|
legendLabels,
|
|
160
160
|
legendInteractive = false,
|
|
161
|
+
gap = 'md',
|
|
161
162
|
className,
|
|
162
163
|
style,
|
|
163
164
|
children,
|
|
164
165
|
} ) => {
|
|
165
166
|
const chartId = useChartId( providedChartId );
|
|
166
167
|
const { leaderboardChart: leaderboardChartSettings } = useGlobalChartsTheme();
|
|
168
|
+
const legendShapeStyles = { width: 8, height: 8, ...legendShapeStylesProp };
|
|
167
169
|
|
|
168
170
|
// Process children to extract compound components
|
|
169
171
|
const { otherChildren } = useChartChildren( children, 'LeaderboardChart' );
|
|
@@ -258,13 +260,21 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
|
|
|
258
260
|
chartHeight: 0,
|
|
259
261
|
} }
|
|
260
262
|
>
|
|
261
|
-
<
|
|
263
|
+
<Stack
|
|
264
|
+
direction="column"
|
|
265
|
+
data-testid="leaderboard-chart-container"
|
|
262
266
|
className={ clsx(
|
|
263
267
|
styles.leaderboardChart,
|
|
268
|
+
{ [ styles[ 'leaderboardChart--responsive' ] ]: ! propWidth && ! propHeight },
|
|
264
269
|
{ [ styles[ 'leaderboardChart--loading' ] ]: loading },
|
|
265
270
|
className
|
|
266
271
|
) }
|
|
267
|
-
|
|
272
|
+
gap={ gap }
|
|
273
|
+
style={ {
|
|
274
|
+
...style,
|
|
275
|
+
width: propWidth || undefined,
|
|
276
|
+
height: propHeight || undefined,
|
|
277
|
+
} }
|
|
268
278
|
>
|
|
269
279
|
<div className={ styles.emptyState }>
|
|
270
280
|
{ loading
|
|
@@ -273,11 +283,23 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
|
|
|
273
283
|
</div>
|
|
274
284
|
{ /* Render children from composition API */ }
|
|
275
285
|
{ otherChildren }
|
|
276
|
-
</
|
|
286
|
+
</Stack>
|
|
277
287
|
</SingleChartContext.Provider>
|
|
278
288
|
);
|
|
279
289
|
}
|
|
280
290
|
|
|
291
|
+
const legendElement = showLegend && (
|
|
292
|
+
<Legend
|
|
293
|
+
orientation={ legendOrientation }
|
|
294
|
+
position={ legendPosition }
|
|
295
|
+
alignment={ legendAlignment }
|
|
296
|
+
shape={ legendShape }
|
|
297
|
+
shapeStyles={ legendShapeStyles }
|
|
298
|
+
chartId={ chartId }
|
|
299
|
+
interactive={ legendInteractive }
|
|
300
|
+
/>
|
|
301
|
+
);
|
|
302
|
+
|
|
281
303
|
return (
|
|
282
304
|
<SingleChartContext.Provider
|
|
283
305
|
value={ {
|
|
@@ -286,76 +308,79 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
|
|
|
286
308
|
chartHeight: 0,
|
|
287
309
|
} }
|
|
288
310
|
>
|
|
289
|
-
<
|
|
311
|
+
<Stack
|
|
312
|
+
direction="column"
|
|
313
|
+
data-testid="leaderboard-chart-container"
|
|
290
314
|
className={ clsx(
|
|
291
315
|
styles.leaderboardChart,
|
|
292
316
|
{
|
|
317
|
+
[ styles[ 'leaderboardChart--responsive' ] ]: ! propWidth && ! propHeight,
|
|
293
318
|
[ styles[ 'leaderboardChart--loading' ] ]: loading,
|
|
294
|
-
[ styles[ 'leaderboardChart--with-legend' ] ]: showLegend,
|
|
295
|
-
[ styles[ 'leaderboardChart--legend-top' ] ]: showLegend && legendPosition === 'top',
|
|
296
319
|
},
|
|
297
320
|
className
|
|
298
321
|
) }
|
|
299
|
-
|
|
322
|
+
gap={ gap }
|
|
323
|
+
style={ {
|
|
324
|
+
...style,
|
|
325
|
+
width: propWidth || undefined,
|
|
326
|
+
height: propHeight || undefined,
|
|
327
|
+
} }
|
|
300
328
|
>
|
|
301
|
-
{
|
|
302
|
-
<div className={ styles.emptyState }>
|
|
303
|
-
{ __( 'All series are hidden. Click legend items to show data.', 'jetpack-charts' ) }
|
|
304
|
-
</div>
|
|
305
|
-
) : (
|
|
306
|
-
<Grid templateColumns="minmax(0, 1fr) auto" rowGap={ rowGap } columnGap={ columnGap }>
|
|
307
|
-
{ data.map( entry => {
|
|
308
|
-
const colorIndex = Math.sign( entry.delta ) + 1;
|
|
309
|
-
const deltaColor = deltaColors[ colorIndex ];
|
|
310
|
-
|
|
311
|
-
return (
|
|
312
|
-
<Fragment key={ entry.id }>
|
|
313
|
-
<VStack spacing={ labelSpacing }>
|
|
314
|
-
<BarWithLabel
|
|
315
|
-
entry={ entry }
|
|
316
|
-
withComparison={ withComparison }
|
|
317
|
-
withOverlayLabel={ withOverlayLabel }
|
|
318
|
-
primaryColor={ resolvedPrimaryColor }
|
|
319
|
-
secondaryColor={ resolvedSecondaryColor }
|
|
320
|
-
isPrimaryVisible={ isPrimaryVisible }
|
|
321
|
-
isComparisonVisible={ isComparisonVisible }
|
|
322
|
-
animation={ animation && ! loading && ! prefersReducedMotion }
|
|
323
|
-
/>
|
|
324
|
-
</VStack>
|
|
325
|
-
|
|
326
|
-
<div
|
|
327
|
-
className={ clsx( styles.valueContainer, {
|
|
328
|
-
[ styles.overlayLabel ]: withOverlayLabel,
|
|
329
|
-
} ) }
|
|
330
|
-
>
|
|
331
|
-
{ isPrimaryVisible && <Text>{ valueFormatter( entry.currentValue ) }</Text> }
|
|
332
|
-
|
|
333
|
-
{ withComparison && isComparisonVisible && (
|
|
334
|
-
<Text style={ { color: deltaColor } }>{ deltaFormatter( entry.delta ) }</Text>
|
|
335
|
-
) }
|
|
336
|
-
</div>
|
|
337
|
-
</Fragment>
|
|
338
|
-
);
|
|
339
|
-
} ) }
|
|
340
|
-
</Grid>
|
|
341
|
-
) }
|
|
329
|
+
{ legendPosition === 'top' && legendElement }
|
|
342
330
|
|
|
343
|
-
{
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
331
|
+
<div className={ styles.leaderboardChart__content }>
|
|
332
|
+
{ allSeriesHidden ? (
|
|
333
|
+
<div className={ styles.emptyState }>
|
|
334
|
+
{ __( 'All series are hidden. Click legend items to show data.', 'jetpack-charts' ) }
|
|
335
|
+
</div>
|
|
336
|
+
) : (
|
|
337
|
+
<Grid templateColumns="minmax(0, 1fr) auto" rowGap={ rowGap } columnGap={ columnGap }>
|
|
338
|
+
{ data.map( entry => {
|
|
339
|
+
const colorIndex = Math.sign( entry.delta ) + 1;
|
|
340
|
+
const deltaColor = deltaColors[ colorIndex ];
|
|
341
|
+
|
|
342
|
+
return (
|
|
343
|
+
<Fragment key={ entry.id }>
|
|
344
|
+
<Stack direction="column" gap={ labelSpacing }>
|
|
345
|
+
<BarWithLabel
|
|
346
|
+
entry={ entry }
|
|
347
|
+
withComparison={ withComparison }
|
|
348
|
+
withOverlayLabel={ withOverlayLabel }
|
|
349
|
+
primaryColor={ resolvedPrimaryColor }
|
|
350
|
+
secondaryColor={ resolvedSecondaryColor }
|
|
351
|
+
isPrimaryVisible={ isPrimaryVisible }
|
|
352
|
+
isComparisonVisible={ isComparisonVisible }
|
|
353
|
+
animation={ animation && ! loading && ! prefersReducedMotion }
|
|
354
|
+
/>
|
|
355
|
+
</Stack>
|
|
356
|
+
|
|
357
|
+
<Stack
|
|
358
|
+
direction="row"
|
|
359
|
+
gap="xs"
|
|
360
|
+
className={ clsx( styles.valueContainer, {
|
|
361
|
+
[ styles.overlayLabel ]: withOverlayLabel,
|
|
362
|
+
} ) }
|
|
363
|
+
>
|
|
364
|
+
{ isPrimaryVisible && <Text>{ valueFormatter( entry.currentValue ) }</Text> }
|
|
365
|
+
|
|
366
|
+
{ withComparison && isComparisonVisible && (
|
|
367
|
+
<Text style={ { color: deltaColor } }>
|
|
368
|
+
{ deltaFormatter( entry.delta ) }
|
|
369
|
+
</Text>
|
|
370
|
+
) }
|
|
371
|
+
</Stack>
|
|
372
|
+
</Fragment>
|
|
373
|
+
);
|
|
374
|
+
} ) }
|
|
375
|
+
</Grid>
|
|
376
|
+
) }
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
{ legendPosition === 'bottom' && legendElement }
|
|
355
380
|
|
|
356
381
|
{ /* Render children from composition API */ }
|
|
357
382
|
{ otherChildren }
|
|
358
|
-
</
|
|
383
|
+
</Stack>
|
|
359
384
|
</SingleChartContext.Provider>
|
|
360
385
|
);
|
|
361
386
|
};
|
|
@@ -2,6 +2,17 @@ import { render, screen } from '@testing-library/react';
|
|
|
2
2
|
import LeaderboardChart from '../leaderboard-chart';
|
|
3
3
|
import type { LeaderboardEntry } from '../../../types';
|
|
4
4
|
|
|
5
|
+
const mockDefaultParentSize = () => ( {
|
|
6
|
+
parentRef: { current: null },
|
|
7
|
+
width: 400,
|
|
8
|
+
height: 300,
|
|
9
|
+
} );
|
|
10
|
+
|
|
11
|
+
// Mock useParentSize so the responsive wrapper returns predictable dimensions in tests
|
|
12
|
+
jest.mock( '@visx/responsive', () => ( {
|
|
13
|
+
useParentSize: jest.fn( () => mockDefaultParentSize() ),
|
|
14
|
+
} ) );
|
|
15
|
+
|
|
5
16
|
const mockData: LeaderboardEntry[] = [
|
|
6
17
|
{
|
|
7
18
|
id: 'direct',
|
|
@@ -40,6 +51,11 @@ const testValueFormatter = ( value: number ) => `${ value }$`;
|
|
|
40
51
|
const testDeltaFormatter = ( value: number ) => `${ value }delta`;
|
|
41
52
|
|
|
42
53
|
describe( 'LeaderboardChart', () => {
|
|
54
|
+
afterEach( () => {
|
|
55
|
+
const { useParentSize } = jest.requireMock( '@visx/responsive' );
|
|
56
|
+
useParentSize.mockImplementation( () => mockDefaultParentSize() );
|
|
57
|
+
} );
|
|
58
|
+
|
|
43
59
|
it( 'renders leaderboard entries', () => {
|
|
44
60
|
render( <LeaderboardChart data={ mockData } /> );
|
|
45
61
|
|
|
@@ -154,8 +170,7 @@ describe( 'LeaderboardChart', () => {
|
|
|
154
170
|
withComparison={ true }
|
|
155
171
|
showLegend={ true }
|
|
156
172
|
legendShape="rect"
|
|
157
|
-
|
|
158
|
-
legendShapeHeight={ 6 }
|
|
173
|
+
legendShapeStyles={ { width: 10, height: 6 } }
|
|
159
174
|
/>
|
|
160
175
|
);
|
|
161
176
|
|
|
@@ -204,7 +219,7 @@ describe( 'LeaderboardChart', () => {
|
|
|
204
219
|
it( 'renders LeaderboardChart.Legend as child component', () => {
|
|
205
220
|
render(
|
|
206
221
|
<LeaderboardChart data={ mockData } withComparison={ true }>
|
|
207
|
-
<LeaderboardChart.Legend
|
|
222
|
+
<LeaderboardChart.Legend />
|
|
208
223
|
</LeaderboardChart>
|
|
209
224
|
);
|
|
210
225
|
|
|
@@ -212,8 +227,8 @@ describe( 'LeaderboardChart', () => {
|
|
|
212
227
|
expect( screen.getByText( 'Direct' ) ).toBeInTheDocument();
|
|
213
228
|
expect( screen.getByText( 'Social Media' ) ).toBeInTheDocument();
|
|
214
229
|
|
|
215
|
-
// Composition legend should render
|
|
216
|
-
expect( screen.getAllByTestId( '
|
|
230
|
+
// Composition legend should render
|
|
231
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
217
232
|
expect( screen.getByText( 'Current period' ) ).toBeInTheDocument();
|
|
218
233
|
expect( screen.getByText( 'Previous period' ) ).toBeInTheDocument();
|
|
219
234
|
} );
|
|
@@ -221,15 +236,12 @@ describe( 'LeaderboardChart', () => {
|
|
|
221
236
|
it( 'renders composition legend regardless of showLegend value', () => {
|
|
222
237
|
render(
|
|
223
238
|
<LeaderboardChart data={ mockData } withComparison={ true } showLegend={ false }>
|
|
224
|
-
<LeaderboardChart.Legend
|
|
239
|
+
<LeaderboardChart.Legend />
|
|
225
240
|
</LeaderboardChart>
|
|
226
241
|
);
|
|
227
242
|
|
|
228
|
-
//
|
|
229
|
-
expect( screen.
|
|
230
|
-
|
|
231
|
-
// Composition legend should still render regardless of showLegend value
|
|
232
|
-
expect( screen.getAllByTestId( 'composition-legend-item' ) ).toHaveLength( 2 );
|
|
243
|
+
// Composition legend should render regardless of showLegend value
|
|
244
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
233
245
|
expect( screen.getByText( 'Current period' ) ).toBeInTheDocument();
|
|
234
246
|
expect( screen.getByText( 'Previous period' ) ).toBeInTheDocument();
|
|
235
247
|
} );
|
|
@@ -237,41 +249,36 @@ describe( 'LeaderboardChart', () => {
|
|
|
237
249
|
it( 'supports both built-in and composition legends simultaneously', () => {
|
|
238
250
|
render(
|
|
239
251
|
<LeaderboardChart data={ mockData } withComparison={ true } showLegend={ true }>
|
|
240
|
-
<LeaderboardChart.Legend
|
|
252
|
+
<LeaderboardChart.Legend />
|
|
241
253
|
</LeaderboardChart>
|
|
242
254
|
);
|
|
243
255
|
|
|
244
|
-
//
|
|
245
|
-
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength(
|
|
246
|
-
|
|
247
|
-
// Composition legend should also render
|
|
248
|
-
expect( screen.getAllByTestId( 'composition-legend-item' ) ).toHaveLength( 2 );
|
|
256
|
+
// Both built-in and composition legends should render (2 items each = 4 total)
|
|
257
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 4 );
|
|
249
258
|
|
|
250
259
|
// Should have legend items from both legends
|
|
251
260
|
const currentPeriodItems = screen.getAllByText( 'Current period' );
|
|
252
261
|
const previousPeriodItems = screen.getAllByText( 'Previous period' );
|
|
253
|
-
expect( currentPeriodItems ).toHaveLength( 2 );
|
|
254
|
-
expect( previousPeriodItems ).toHaveLength( 2 );
|
|
262
|
+
expect( currentPeriodItems ).toHaveLength( 2 );
|
|
263
|
+
expect( previousPeriodItems ).toHaveLength( 2 );
|
|
255
264
|
} );
|
|
256
265
|
|
|
257
266
|
it( 'passes props correctly to composition legend', () => {
|
|
258
267
|
render(
|
|
259
268
|
<LeaderboardChart data={ mockData } withComparison={ true }>
|
|
260
|
-
<LeaderboardChart.Legend
|
|
261
|
-
data-testid="composition-legend-item"
|
|
262
|
-
shape="circle"
|
|
263
|
-
shapeWidth={ 12 }
|
|
264
|
-
shapeHeight={ 12 }
|
|
265
|
-
style={ { marginTop: '20px' } }
|
|
266
|
-
/>
|
|
269
|
+
<LeaderboardChart.Legend shape="circle" shapeStyles={ { margin: '4px 8px' } } />
|
|
267
270
|
</LeaderboardChart>
|
|
268
271
|
);
|
|
269
272
|
|
|
270
|
-
const legendItems = screen.getAllByTestId( '
|
|
273
|
+
const legendItems = screen.getAllByTestId( 'legend-item' );
|
|
271
274
|
expect( legendItems ).toHaveLength( 2 );
|
|
272
|
-
|
|
275
|
+
|
|
276
|
+
// Verify custom shape styles are applied within each legend item.
|
|
277
|
+
// Direct DOM access is needed because visx legend shapes lack accessible attributes and we cannot pass a test id to them.
|
|
273
278
|
legendItems.forEach( item => {
|
|
274
|
-
|
|
279
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
280
|
+
const shape = item.querySelector( '.visx-legend-shape' );
|
|
281
|
+
expect( shape ).toHaveStyle( { margin: '4px 8px' } );
|
|
275
282
|
} );
|
|
276
283
|
} );
|
|
277
284
|
|
|
@@ -339,4 +346,26 @@ describe( 'LeaderboardChart', () => {
|
|
|
339
346
|
expect( screen.getByText( '-8%' ) ).toBeInTheDocument();
|
|
340
347
|
} );
|
|
341
348
|
} );
|
|
349
|
+
|
|
350
|
+
describe( 'Responsive wrapper', () => {
|
|
351
|
+
it( 'fills parent container (height:100%) by default', () => {
|
|
352
|
+
render( <LeaderboardChart data={ mockData } /> );
|
|
353
|
+
const wrapper = screen.getByTestId( 'responsive-wrapper' );
|
|
354
|
+
expect( wrapper ).toHaveStyle( { height: '100%' } );
|
|
355
|
+
} );
|
|
356
|
+
|
|
357
|
+
it( 'applies explicit width and height to chart container', () => {
|
|
358
|
+
const { useParentSize } = jest.requireMock( '@visx/responsive' );
|
|
359
|
+
useParentSize.mockReturnValue( {
|
|
360
|
+
parentRef: { current: null },
|
|
361
|
+
width: 0,
|
|
362
|
+
height: 0,
|
|
363
|
+
} );
|
|
364
|
+
|
|
365
|
+
render( <LeaderboardChart data={ mockData } width={ 500 } height={ 240 } /> );
|
|
366
|
+
const chartContainer = screen.getByTestId( 'leaderboard-chart-container' );
|
|
367
|
+
|
|
368
|
+
expect( chartContainer ).toHaveStyle( { width: '500px', height: '240px' } );
|
|
369
|
+
} );
|
|
370
|
+
} );
|
|
342
371
|
} );
|
|
@@ -80,7 +80,6 @@ describe( 'useLeaderboardLegendItems', () => {
|
|
|
80
80
|
expect( result.current ).toHaveLength( 1 );
|
|
81
81
|
expect( result.current[ 0 ] ).toEqual( {
|
|
82
82
|
label: 'Current period',
|
|
83
|
-
value: '',
|
|
84
83
|
color: expect.any( String ),
|
|
85
84
|
} );
|
|
86
85
|
} );
|
|
@@ -102,14 +101,12 @@ describe( 'useLeaderboardLegendItems', () => {
|
|
|
102
101
|
// Current period item
|
|
103
102
|
expect( result.current[ 0 ] ).toEqual( {
|
|
104
103
|
label: 'Current period',
|
|
105
|
-
value: '',
|
|
106
104
|
color: expect.any( String ),
|
|
107
105
|
} );
|
|
108
106
|
|
|
109
107
|
// Previous period item
|
|
110
108
|
expect( result.current[ 1 ] ).toEqual( {
|
|
111
109
|
label: 'Previous period',
|
|
112
|
-
value: '',
|
|
113
110
|
color: expect.any( String ),
|
|
114
111
|
} );
|
|
115
112
|
} );
|
|
@@ -585,7 +582,7 @@ describe( 'useLeaderboardLegendItems', () => {
|
|
|
585
582
|
expect( result.current[ 1 ].label ).toBe( 'Previous period' );
|
|
586
583
|
} );
|
|
587
584
|
|
|
588
|
-
it( 'should
|
|
585
|
+
it( 'should not include value property for legend items', () => {
|
|
589
586
|
const wrapper = createWrapper();
|
|
590
587
|
const { result } = renderHook(
|
|
591
588
|
() =>
|
|
@@ -598,7 +595,7 @@ describe( 'useLeaderboardLegendItems', () => {
|
|
|
598
595
|
);
|
|
599
596
|
|
|
600
597
|
result.current.forEach( item => {
|
|
601
|
-
expect( item
|
|
598
|
+
expect( item ).not.toHaveProperty( 'value' );
|
|
602
599
|
} );
|
|
603
600
|
} );
|
|
604
601
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import { BaseChartProps, LeaderboardEntry } from '../../types';
|
|
3
|
+
import type { LegendShapeStyles } from '../../components/legend';
|
|
3
4
|
|
|
4
5
|
export interface LeaderboardChartProps
|
|
5
6
|
extends Pick<
|
|
@@ -15,6 +16,7 @@ export interface LeaderboardChartProps
|
|
|
15
16
|
| 'width'
|
|
16
17
|
| 'height'
|
|
17
18
|
| 'size'
|
|
19
|
+
| 'gap'
|
|
18
20
|
| 'legendInteractive'
|
|
19
21
|
| 'animation'
|
|
20
22
|
> {
|
|
@@ -61,14 +63,9 @@ export interface LeaderboardChartProps
|
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
/**
|
|
64
|
-
*
|
|
66
|
+
* Styles for legend shapes (width, height, margin).
|
|
65
67
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Height of legend shapes in pixels
|
|
70
|
-
*/
|
|
71
|
-
legendShapeHeight?: number;
|
|
68
|
+
legendShapeStyles?: LegendShapeStyles;
|
|
72
69
|
|
|
73
70
|
/**
|
|
74
71
|
* Custom labels for legend items
|
|
@@ -457,9 +457,8 @@ const LineChartInternal = forwardRef< SingleChartRef, LineChartProps >(
|
|
|
457
457
|
orientation={ legendOrientation }
|
|
458
458
|
alignment={ legendAlignment }
|
|
459
459
|
position={ legendPosition }
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
legendItemClassName={ legendItemClassName }
|
|
460
|
+
labelStyles={ { maxWidth: legendMaxWidth, textOverflow: legendTextOverflow } }
|
|
461
|
+
itemClassName={ legendItemClassName }
|
|
463
462
|
className={ styles[ 'line-chart__legend' ] }
|
|
464
463
|
shape={ legendShape }
|
|
465
464
|
chartId={ chartId }
|
|
@@ -317,9 +317,8 @@ const PieChartInternal = ( {
|
|
|
317
317
|
orientation={ legendOrientation }
|
|
318
318
|
position={ legendPosition }
|
|
319
319
|
alignment={ legendAlignment }
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
legendItemClassName={ legendItemClassName }
|
|
320
|
+
labelStyles={ { maxWidth: legendMaxWidth, textOverflow: legendTextOverflow } }
|
|
321
|
+
itemClassName={ legendItemClassName }
|
|
323
322
|
shape={ legendShape }
|
|
324
323
|
chartId={ chartId }
|
|
325
324
|
interactive={ legendInteractive }
|
|
@@ -352,9 +352,8 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
352
352
|
orientation={ legendOrientation }
|
|
353
353
|
position={ legendPosition }
|
|
354
354
|
alignment={ legendAlignment }
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
legendItemClassName={ legendItemClassName }
|
|
355
|
+
labelStyles={ { maxWidth: legendMaxWidth, textOverflow: legendTextOverflow } }
|
|
356
|
+
itemClassName={ legendItemClassName }
|
|
358
357
|
shape={ legendShape }
|
|
359
358
|
chartId={ chartId }
|
|
360
359
|
interactive={ legendInteractive }
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export { Legend } from './legend';
|
|
2
2
|
export { useChartLegendItems } from './hooks/use-chart-legend-items';
|
|
3
|
-
export type {
|
|
3
|
+
export type {
|
|
4
|
+
LegendProps,
|
|
5
|
+
BaseLegendProps,
|
|
6
|
+
BaseLegendItem,
|
|
7
|
+
LegendItemStyles,
|
|
8
|
+
LegendLabelStyles,
|
|
9
|
+
LegendShapeStyles,
|
|
10
|
+
} from './types';
|
|
4
11
|
export type { ChartLegendOptions, LegendValueDisplay } from './hooks/use-chart-legend-items';
|