@automattic/charts 0.44.0 → 0.46.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 +14 -0
- package/dist/{chunk-2HB55BRH.js → chunk-4H3J2HCD.js} +102 -47
- package/dist/chunk-4H3J2HCD.js.map +1 -0
- package/dist/{chunk-G4FX5I3V.cjs → chunk-7AH76DXF.cjs} +119 -64
- package/dist/chunk-7AH76DXF.cjs.map +1 -0
- package/dist/{chunk-3O6FHD2T.js → chunk-A3PGOCJO.js} +46 -4
- package/dist/chunk-A3PGOCJO.js.map +1 -0
- package/dist/{chunk-G66WE3ON.js → chunk-BWEMZ72V.js} +41 -15
- package/dist/chunk-BWEMZ72V.js.map +1 -0
- package/dist/{chunk-BZ6UDD37.cjs → chunk-CNAKHZMW.cjs} +69 -31
- package/dist/chunk-CNAKHZMW.cjs.map +1 -0
- package/dist/{chunk-MAV6SE6L.cjs → chunk-GEB4GELE.cjs} +24 -24
- package/dist/{chunk-MAV6SE6L.cjs.map → chunk-GEB4GELE.cjs.map} +1 -1
- package/dist/{chunk-KM62I6SD.js → chunk-HVWETEEV.js} +53 -15
- package/dist/chunk-HVWETEEV.js.map +1 -0
- package/dist/{chunk-HYHBAHIU.js → chunk-JGX3ZNK5.js} +3 -3
- package/dist/{chunk-W5RFMC3A.js → chunk-JI6OGGGF.js} +3 -3
- package/dist/{chunk-SHADFB3T.js → chunk-KEBKTDOQ.js} +2 -2
- package/dist/{chunk-2HUX2CAT.cjs → chunk-LSGYIUQX.cjs} +44 -2
- package/dist/chunk-LSGYIUQX.cjs.map +1 -0
- package/dist/{chunk-UHESRL2F.cjs → chunk-N36WJKYM.cjs} +6 -6
- package/dist/{chunk-UHESRL2F.cjs.map → chunk-N36WJKYM.cjs.map} +1 -1
- package/dist/{chunk-Q2LDRQN7.js → chunk-PFT2X4OW.js} +2 -2
- package/dist/{chunk-GK3XEXVI.cjs → chunk-PNSMPZ3E.cjs} +8 -8
- package/dist/{chunk-GK3XEXVI.cjs.map → chunk-PNSMPZ3E.cjs.map} +1 -1
- package/dist/{chunk-SC462VDM.cjs → chunk-QPHNEQCK.cjs} +11 -11
- package/dist/{chunk-SC462VDM.cjs.map → chunk-QPHNEQCK.cjs.map} +1 -1
- package/dist/{chunk-ZA7OWPY7.cjs → chunk-VOMSG7KV.cjs} +50 -24
- package/dist/chunk-VOMSG7KV.cjs.map +1 -0
- package/dist/{chunk-QLLKOSJ6.cjs → chunk-YKVKFUV7.cjs} +50 -24
- package/dist/chunk-YKVKFUV7.cjs.map +1 -0
- package/dist/{chunk-XDIWMJZD.js → chunk-ZSNO2BYX.js} +39 -13
- package/dist/chunk-ZSNO2BYX.js.map +1 -0
- package/dist/components/bar-chart/index.cjs +4 -4
- package/dist/components/bar-chart/index.d.cts +2 -1
- package/dist/components/bar-chart/index.d.ts +2 -1
- package/dist/components/bar-chart/index.js +3 -3
- package/dist/components/bar-list-chart/index.cjs +5 -5
- package/dist/components/bar-list-chart/index.d.cts +1 -1
- package/dist/components/bar-list-chart/index.d.ts +1 -1
- package/dist/components/bar-list-chart/index.js +4 -4
- package/dist/components/conversion-funnel-chart/index.cjs +3 -3
- package/dist/components/conversion-funnel-chart/index.d.cts +1 -1
- package/dist/components/conversion-funnel-chart/index.d.ts +1 -1
- package/dist/components/conversion-funnel-chart/index.js +2 -2
- package/dist/components/leaderboard-chart/index.cjs +4 -4
- package/dist/components/leaderboard-chart/index.d.cts +2 -2
- package/dist/components/leaderboard-chart/index.d.ts +2 -2
- package/dist/components/leaderboard-chart/index.js +3 -3
- package/dist/components/legend/index.cjs +3 -3
- package/dist/components/legend/index.d.cts +1 -1
- package/dist/components/legend/index.d.ts +1 -1
- package/dist/components/legend/index.js +2 -2
- package/dist/components/line-chart/index.cjs +4 -4
- package/dist/components/line-chart/index.d.cts +1 -1
- package/dist/components/line-chart/index.d.ts +1 -1
- package/dist/components/line-chart/index.js +3 -3
- package/dist/components/pie-chart/index.cjs +4 -4
- package/dist/components/pie-chart/index.d.cts +7 -1
- package/dist/components/pie-chart/index.d.ts +7 -1
- package/dist/components/pie-chart/index.js +3 -3
- package/dist/components/pie-semi-circle-chart/index.cjs +4 -4
- package/dist/components/pie-semi-circle-chart/index.d.cts +7 -1
- package/dist/components/pie-semi-circle-chart/index.d.ts +7 -1
- package/dist/components/pie-semi-circle-chart/index.js +3 -3
- package/dist/components/tooltip/index.d.cts +1 -1
- package/dist/components/tooltip/index.d.ts +1 -1
- package/dist/hooks/index.cjs +4 -2
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +79 -2
- package/dist/hooks/index.d.ts +79 -2
- package/dist/hooks/index.js +3 -1
- package/dist/index.cjs +10 -10
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +9 -9
- package/dist/{leaderboard-chart-BWEheWCd.d.cts → leaderboard-chart-B5JRimc9.d.cts} +2 -2
- package/dist/{leaderboard-chart-rqyTz1m6.d.ts → leaderboard-chart-DQ8i8GMA.d.ts} +2 -2
- package/dist/providers/index.cjs +2 -2
- package/dist/providers/index.d.cts +2 -2
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.js +1 -1
- package/dist/{themes-CGUHFZ5g.d.ts → themes-CN85BQM1.d.ts} +1 -1
- package/dist/{themes-B4swlmql.d.cts → themes-TIJq1lG_.d.cts} +1 -1
- package/dist/{types-cEbX_Q2K.d.ts → types-73KOEWs9.d.cts} +3 -1
- package/dist/{types-cEbX_Q2K.d.cts → types-73KOEWs9.d.ts} +3 -1
- package/package.json +3 -3
- package/src/components/bar-chart/bar-chart.tsx +57 -11
- package/src/components/bar-chart/test/bar-chart.test.tsx +114 -0
- package/src/components/leaderboard-chart/leaderboard-chart.tsx +85 -38
- package/src/components/leaderboard-chart/test/leaderboard-chart.test.tsx +48 -0
- package/src/components/leaderboard-chart/types.ts +1 -0
- package/src/components/pie-chart/pie-chart.tsx +130 -93
- package/src/components/pie-chart/test/pie-chart.test.tsx +174 -0
- package/src/components/pie-semi-circle-chart/pie-semi-circle-chart.tsx +96 -57
- package/src/components/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +84 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-interactive-legend-data.ts +138 -0
- package/src/types.ts +3 -1
- package/dist/chunk-2HB55BRH.js.map +0 -1
- package/dist/chunk-2HUX2CAT.cjs.map +0 -1
- package/dist/chunk-3O6FHD2T.js.map +0 -1
- package/dist/chunk-BZ6UDD37.cjs.map +0 -1
- package/dist/chunk-G4FX5I3V.cjs.map +0 -1
- package/dist/chunk-G66WE3ON.js.map +0 -1
- package/dist/chunk-KM62I6SD.js.map +0 -1
- package/dist/chunk-QLLKOSJ6.cjs.map +0 -1
- package/dist/chunk-XDIWMJZD.js.map +0 -1
- package/dist/chunk-ZA7OWPY7.cjs.map +0 -1
- /package/dist/{chunk-HYHBAHIU.js.map → chunk-JGX3ZNK5.js.map} +0 -0
- /package/dist/{chunk-W5RFMC3A.js.map → chunk-JI6OGGGF.js.map} +0 -0
- /package/dist/{chunk-SHADFB3T.js.map → chunk-KEBKTDOQ.js.map} +0 -0
- /package/dist/{chunk-Q2LDRQN7.js.map → chunk-PFT2X4OW.js.map} +0 -0
|
@@ -344,7 +344,9 @@ type BaseChartProps<T = DataPoint | DataPointDate | LeaderboardEntry> = {
|
|
|
344
344
|
legendItemClassName?: string;
|
|
345
345
|
/**
|
|
346
346
|
* Enable interactive legend items that can toggle series visibility.
|
|
347
|
-
*
|
|
347
|
+
* Supported for LineChart, PieChart, and PieSemiCircleChart.
|
|
348
|
+
* Requires chartId and GlobalChartsProvider.
|
|
349
|
+
* For pie charts, percentages are recalculated so visible segments total 100%.
|
|
348
350
|
*/
|
|
349
351
|
legendInteractive?: boolean;
|
|
350
352
|
/**
|
|
@@ -344,7 +344,9 @@ type BaseChartProps<T = DataPoint | DataPointDate | LeaderboardEntry> = {
|
|
|
344
344
|
legendItemClassName?: string;
|
|
345
345
|
/**
|
|
346
346
|
* Enable interactive legend items that can toggle series visibility.
|
|
347
|
-
*
|
|
347
|
+
* Supported for LineChart, PieChart, and PieSemiCircleChart.
|
|
348
|
+
* Requires chartId and GlobalChartsProvider.
|
|
349
|
+
* For pie charts, percentages are recalculated so visible segments total 100%.
|
|
348
350
|
*/
|
|
349
351
|
legendInteractive?: boolean;
|
|
350
352
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/charts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "Display charts within Automattic products.",
|
|
5
5
|
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/charts/#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
"typecheck": "tsc --noEmit"
|
|
151
151
|
},
|
|
152
152
|
"dependencies": {
|
|
153
|
-
"@automattic/number-formatters": "^1.0.
|
|
153
|
+
"@automattic/number-formatters": "^1.0.14",
|
|
154
154
|
"@babel/runtime": "7.28.4",
|
|
155
155
|
"@react-spring/web": "9.7.5",
|
|
156
156
|
"@visx/annotation": "^3.12.0",
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
"sass-loader": "^16.0.0",
|
|
217
217
|
"storybook": "9.0.15",
|
|
218
218
|
"tsup": "8.5.0",
|
|
219
|
-
"typescript": "5.9.
|
|
219
|
+
"typescript": "5.9.3",
|
|
220
220
|
"webpack": "^5.88.0",
|
|
221
221
|
"webpack-cli": "^6.0.0"
|
|
222
222
|
},
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
useChartId,
|
|
16
16
|
useChartRegistration,
|
|
17
17
|
useGlobalChartsContext,
|
|
18
|
+
useGlobalChartsTheme,
|
|
18
19
|
GlobalChartsContext,
|
|
19
20
|
} from '../../providers';
|
|
20
21
|
import { attachSubComponents } from '../../utils';
|
|
@@ -34,6 +35,7 @@ export interface BarChartProps extends BaseChartProps< SeriesData[] > {
|
|
|
34
35
|
orientation?: 'horizontal' | 'vertical';
|
|
35
36
|
withPatterns?: boolean;
|
|
36
37
|
showZeroValues?: boolean;
|
|
38
|
+
legendInteractive?: boolean;
|
|
37
39
|
children?: ReactNode;
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -92,6 +94,7 @@ const BarChartInternal: FC< BarChartProps > = ( {
|
|
|
92
94
|
orientation = 'vertical',
|
|
93
95
|
withPatterns = false,
|
|
94
96
|
showZeroValues = false,
|
|
97
|
+
legendInteractive = false,
|
|
95
98
|
children,
|
|
96
99
|
} ) => {
|
|
97
100
|
const horizontal = orientation === 'horizontal';
|
|
@@ -127,7 +130,29 @@ const BarChartInternal: FC< BarChartProps > = ( {
|
|
|
127
130
|
totalPoints,
|
|
128
131
|
} );
|
|
129
132
|
|
|
130
|
-
const { getElementStyles } = useGlobalChartsContext();
|
|
133
|
+
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
134
|
+
const providerTheme = useGlobalChartsTheme();
|
|
135
|
+
|
|
136
|
+
// Add visibility information to series when using interactive legends
|
|
137
|
+
const seriesWithVisibility = useMemo( () => {
|
|
138
|
+
if ( ! chartId || ! legendInteractive ) {
|
|
139
|
+
return dataWithVisibleZeros.map( ( series, index ) => ( {
|
|
140
|
+
series,
|
|
141
|
+
index,
|
|
142
|
+
isVisible: true,
|
|
143
|
+
} ) );
|
|
144
|
+
}
|
|
145
|
+
return dataWithVisibleZeros.map( ( series, index ) => ( {
|
|
146
|
+
series,
|
|
147
|
+
index,
|
|
148
|
+
isVisible: isSeriesVisible( chartId, series.label ),
|
|
149
|
+
} ) );
|
|
150
|
+
}, [ dataWithVisibleZeros, chartId, isSeriesVisible, legendInteractive ] );
|
|
151
|
+
|
|
152
|
+
// Check if all series are hidden
|
|
153
|
+
const allSeriesHidden = useMemo( () => {
|
|
154
|
+
return seriesWithVisibility.every( ( { isVisible } ) => ! isVisible );
|
|
155
|
+
}, [ seriesWithVisibility ] );
|
|
131
156
|
|
|
132
157
|
const getBarBackground = useCallback(
|
|
133
158
|
( index: number ) => () =>
|
|
@@ -348,17 +373,37 @@ const BarChartInternal: FC< BarChartProps > = ( {
|
|
|
348
373
|
|
|
349
374
|
{ highlightedBarStyle && <style>{ highlightedBarStyle }</style> }
|
|
350
375
|
|
|
376
|
+
{ allSeriesHidden ? (
|
|
377
|
+
<text
|
|
378
|
+
x={ width / 2 }
|
|
379
|
+
y={ ( height - ( showLegend ? legendHeight : 0 ) ) / 2 }
|
|
380
|
+
textAnchor="middle"
|
|
381
|
+
fill={ providerTheme.gridStyles?.stroke || '#ccc' }
|
|
382
|
+
fontSize="14"
|
|
383
|
+
fontFamily="-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,sans-serif"
|
|
384
|
+
>
|
|
385
|
+
{ __( 'All series are hidden. Click legend items to show data.', 'jetpack-charts' ) }
|
|
386
|
+
</text>
|
|
387
|
+
) : null }
|
|
388
|
+
|
|
351
389
|
<BarGroup padding={ chartOptions.barGroup.padding }>
|
|
352
|
-
{
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
390
|
+
{ seriesWithVisibility.map( ( { series: seriesData, index, isVisible } ) => {
|
|
391
|
+
// Skip rendering invisible series
|
|
392
|
+
if ( ! isVisible ) {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<BarSeries
|
|
398
|
+
key={ seriesData?.label }
|
|
399
|
+
dataKey={ seriesData?.label }
|
|
400
|
+
data={ seriesData.data as DataPointDate[] }
|
|
401
|
+
yAccessor={ chartOptions.accessors.yAccessor }
|
|
402
|
+
xAccessor={ chartOptions.accessors.xAccessor }
|
|
403
|
+
colorAccessor={ getBarBackground( index ) }
|
|
404
|
+
/>
|
|
405
|
+
);
|
|
406
|
+
} ) }
|
|
362
407
|
</BarGroup>
|
|
363
408
|
|
|
364
409
|
<Axis { ...chartOptions.axis.x } />
|
|
@@ -391,6 +436,7 @@ const BarChartInternal: FC< BarChartProps > = ( {
|
|
|
391
436
|
shape={ legendShape }
|
|
392
437
|
ref={ legendRef }
|
|
393
438
|
chartId={ chartId }
|
|
439
|
+
interactive={ legendInteractive }
|
|
394
440
|
/>
|
|
395
441
|
) }
|
|
396
442
|
|
|
@@ -563,4 +563,118 @@ describe( 'BarChart', () => {
|
|
|
563
563
|
} );
|
|
564
564
|
|
|
565
565
|
/* eslint-enable testing-library/no-node-access */
|
|
566
|
+
|
|
567
|
+
describe( 'Interactive Legend', () => {
|
|
568
|
+
it( 'filters series when interactive legend is enabled and series is toggled', async () => {
|
|
569
|
+
const user = userEvent.setup();
|
|
570
|
+
|
|
571
|
+
renderWithTheme( {
|
|
572
|
+
showLegend: true,
|
|
573
|
+
legendInteractive: true,
|
|
574
|
+
chartId: 'test-interactive-bar-chart',
|
|
575
|
+
data: [
|
|
576
|
+
{
|
|
577
|
+
label: 'Series A',
|
|
578
|
+
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
579
|
+
options: {},
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
label: 'Series B',
|
|
583
|
+
data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
|
|
584
|
+
options: {},
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
} );
|
|
588
|
+
|
|
589
|
+
// Click on first legend item to hide it
|
|
590
|
+
const legendItems = screen.getAllByRole( 'button' );
|
|
591
|
+
await user.click( legendItems[ 0 ] );
|
|
592
|
+
|
|
593
|
+
// The series should now be hidden (aria-pressed = false)
|
|
594
|
+
const legendItem = screen.getAllByRole( 'button' )[ 0 ];
|
|
595
|
+
expect( legendItem ).toHaveAttribute( 'aria-pressed', 'false' );
|
|
596
|
+
} );
|
|
597
|
+
|
|
598
|
+
it( 'does not filter series when legendInteractive is false', () => {
|
|
599
|
+
renderWithTheme( {
|
|
600
|
+
showLegend: true,
|
|
601
|
+
legendInteractive: false,
|
|
602
|
+
chartId: 'test-non-interactive-bar-chart',
|
|
603
|
+
data: [
|
|
604
|
+
{
|
|
605
|
+
label: 'Series A',
|
|
606
|
+
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
607
|
+
options: {},
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
label: 'Series B',
|
|
611
|
+
data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
|
|
612
|
+
options: {},
|
|
613
|
+
},
|
|
614
|
+
],
|
|
615
|
+
} );
|
|
616
|
+
|
|
617
|
+
// Legend items should not be interactive
|
|
618
|
+
const buttons = screen.queryAllByRole( 'button' );
|
|
619
|
+
expect( buttons ).toHaveLength( 0 );
|
|
620
|
+
} );
|
|
621
|
+
|
|
622
|
+
it( 'shows all series when chartId is missing even if legendInteractive is true', () => {
|
|
623
|
+
renderWithTheme( {
|
|
624
|
+
showLegend: true,
|
|
625
|
+
legendInteractive: true,
|
|
626
|
+
// No chartId provided
|
|
627
|
+
data: [
|
|
628
|
+
{
|
|
629
|
+
label: 'Series A',
|
|
630
|
+
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
631
|
+
options: {},
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
label: 'Series B',
|
|
635
|
+
data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
|
|
636
|
+
options: {},
|
|
637
|
+
},
|
|
638
|
+
],
|
|
639
|
+
} );
|
|
640
|
+
|
|
641
|
+
// All legend items should be visible (not hidden)
|
|
642
|
+
const legendItems = screen.getAllByRole( 'button' );
|
|
643
|
+
legendItems.forEach( item => {
|
|
644
|
+
expect( item ).toHaveAttribute( 'aria-pressed', 'true' );
|
|
645
|
+
} );
|
|
646
|
+
} );
|
|
647
|
+
|
|
648
|
+
it( 'shows "All series are hidden" message when all series are toggled off', async () => {
|
|
649
|
+
const user = userEvent.setup();
|
|
650
|
+
|
|
651
|
+
renderWithTheme( {
|
|
652
|
+
showLegend: true,
|
|
653
|
+
legendInteractive: true,
|
|
654
|
+
chartId: 'test-all-hidden-bar-chart',
|
|
655
|
+
data: [
|
|
656
|
+
{
|
|
657
|
+
label: 'Series A',
|
|
658
|
+
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
659
|
+
options: {},
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
label: 'Series B',
|
|
663
|
+
data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
|
|
664
|
+
options: {},
|
|
665
|
+
},
|
|
666
|
+
],
|
|
667
|
+
} );
|
|
668
|
+
|
|
669
|
+
// Hide all series
|
|
670
|
+
const legendItems = screen.getAllByRole( 'button' );
|
|
671
|
+
await user.click( legendItems[ 0 ] );
|
|
672
|
+
await user.click( legendItems[ 1 ] );
|
|
673
|
+
|
|
674
|
+
// Check for the "all series hidden" message
|
|
675
|
+
expect(
|
|
676
|
+
screen.getByText( /all series are hidden.*click legend items to show data/i )
|
|
677
|
+
).toBeInTheDocument();
|
|
678
|
+
} );
|
|
679
|
+
} );
|
|
566
680
|
} );
|
|
@@ -62,12 +62,16 @@ const BarWithLabel = ( {
|
|
|
62
62
|
withOverlayLabel,
|
|
63
63
|
primaryColor,
|
|
64
64
|
secondaryColor,
|
|
65
|
+
isPrimaryVisible = true,
|
|
66
|
+
isComparisonVisible = true,
|
|
65
67
|
}: {
|
|
66
68
|
entry: LeaderboardEntry;
|
|
67
69
|
withComparison?: boolean;
|
|
68
70
|
withOverlayLabel?: boolean;
|
|
69
71
|
primaryColor: string;
|
|
70
72
|
secondaryColor: string;
|
|
73
|
+
isPrimaryVisible?: boolean;
|
|
74
|
+
isComparisonVisible?: boolean;
|
|
71
75
|
} ) => (
|
|
72
76
|
<div
|
|
73
77
|
className={ clsx( styles.barWithLabelContainer, {
|
|
@@ -76,15 +80,17 @@ const BarWithLabel = ( {
|
|
|
76
80
|
>
|
|
77
81
|
<BarLabel label={ entry.label } />
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
{ isPrimaryVisible && (
|
|
84
|
+
<div
|
|
85
|
+
className={ styles.bar }
|
|
86
|
+
style={ {
|
|
87
|
+
width: entry.currentShare + '%',
|
|
88
|
+
backgroundColor: primaryColor,
|
|
89
|
+
} }
|
|
90
|
+
></div>
|
|
91
|
+
) }
|
|
86
92
|
|
|
87
|
-
{ withComparison && ! withOverlayLabel && (
|
|
93
|
+
{ withComparison && ! withOverlayLabel && isComparisonVisible && (
|
|
88
94
|
<div
|
|
89
95
|
className={ styles.bar }
|
|
90
96
|
style={ {
|
|
@@ -118,6 +124,7 @@ const BarWithLabel = ( {
|
|
|
118
124
|
* @param props.legendShapeWidth - Width of legend shapes in pixels
|
|
119
125
|
* @param props.legendShapeHeight - Height of legend shapes in pixels
|
|
120
126
|
* @param props.legendLabels - Custom labels for legend items
|
|
127
|
+
* @param props.legendInteractive - Whether legend items are interactive (clickable to toggle series visibility)
|
|
121
128
|
* @param props.children - Child components for composition API
|
|
122
129
|
* @param props.className - Additional CSS class name
|
|
123
130
|
* @param props.style - Custom styling for the chart container
|
|
@@ -141,6 +148,7 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
|
|
|
141
148
|
legendShapeWidth = 8,
|
|
142
149
|
legendShapeHeight = 8,
|
|
143
150
|
legendLabels,
|
|
151
|
+
legendInteractive = false,
|
|
144
152
|
className,
|
|
145
153
|
style,
|
|
146
154
|
children,
|
|
@@ -158,7 +166,7 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
|
|
|
158
166
|
secondaryColor: settingsSecondaryColor,
|
|
159
167
|
deltaColors,
|
|
160
168
|
} = leaderboardChartSettings;
|
|
161
|
-
const { getElementStyles } = useGlobalChartsContext();
|
|
169
|
+
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
162
170
|
const { color: resolvedPrimaryColor } = getElementStyles( {
|
|
163
171
|
index: 0,
|
|
164
172
|
overrideColor: primaryColor || settingsPrimaryColor,
|
|
@@ -178,6 +186,36 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
|
|
|
178
186
|
legendLabels,
|
|
179
187
|
} );
|
|
180
188
|
|
|
189
|
+
// Track visibility of primary and comparison series for interactive legends
|
|
190
|
+
const isPrimaryVisible = useMemo( () => {
|
|
191
|
+
if ( ! chartId || ! legendInteractive || legendItems.length === 0 ) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
return isSeriesVisible( chartId, legendItems[ 0 ].label );
|
|
195
|
+
}, [ chartId, legendInteractive, legendItems, isSeriesVisible ] );
|
|
196
|
+
|
|
197
|
+
const isComparisonVisible = useMemo( () => {
|
|
198
|
+
if ( ! chartId || ! legendInteractive || legendItems.length < 2 ) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
return isSeriesVisible( chartId, legendItems[ 1 ].label );
|
|
202
|
+
}, [ chartId, legendInteractive, legendItems, isSeriesVisible ] );
|
|
203
|
+
|
|
204
|
+
// Check if all series are hidden
|
|
205
|
+
const allSeriesHidden = useMemo( () => {
|
|
206
|
+
if ( ! legendInteractive ) return false;
|
|
207
|
+
if ( withComparison && ! withOverlayLabel ) {
|
|
208
|
+
return ! isPrimaryVisible && ! isComparisonVisible;
|
|
209
|
+
}
|
|
210
|
+
return ! isPrimaryVisible;
|
|
211
|
+
}, [
|
|
212
|
+
legendInteractive,
|
|
213
|
+
isPrimaryVisible,
|
|
214
|
+
isComparisonVisible,
|
|
215
|
+
withComparison,
|
|
216
|
+
withOverlayLabel,
|
|
217
|
+
] );
|
|
218
|
+
|
|
181
219
|
// Validate data
|
|
182
220
|
const isDataValid = Boolean( data && data.length > 0 );
|
|
183
221
|
|
|
@@ -242,38 +280,46 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
|
|
|
242
280
|
gap: showLegend ? '16px' : '0',
|
|
243
281
|
} }
|
|
244
282
|
>
|
|
245
|
-
|
|
246
|
-
{
|
|
247
|
-
|
|
248
|
-
|
|
283
|
+
{ allSeriesHidden ? (
|
|
284
|
+
<div className={ styles.emptyState }>
|
|
285
|
+
{ __( 'All series are hidden. Click legend items to show data.', 'jetpack-charts' ) }
|
|
286
|
+
</div>
|
|
287
|
+
) : (
|
|
288
|
+
<Grid templateColumns="minmax(0, 1fr) auto" rowGap={ rowGap } columnGap={ columnGap }>
|
|
289
|
+
{ data.map( entry => {
|
|
290
|
+
const colorIndex = Math.sign( entry.delta ) + 1;
|
|
291
|
+
const deltaColor = deltaColors[ colorIndex ];
|
|
249
292
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
293
|
+
return (
|
|
294
|
+
<Fragment key={ entry.id }>
|
|
295
|
+
<VStack spacing={ labelSpacing }>
|
|
296
|
+
<BarWithLabel
|
|
297
|
+
entry={ entry }
|
|
298
|
+
withComparison={ withComparison }
|
|
299
|
+
withOverlayLabel={ withOverlayLabel }
|
|
300
|
+
primaryColor={ resolvedPrimaryColor }
|
|
301
|
+
secondaryColor={ resolvedSecondaryColor }
|
|
302
|
+
isPrimaryVisible={ isPrimaryVisible }
|
|
303
|
+
isComparisonVisible={ isComparisonVisible }
|
|
304
|
+
/>
|
|
305
|
+
</VStack>
|
|
261
306
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
307
|
+
<div
|
|
308
|
+
className={ clsx( styles.valueContainer, {
|
|
309
|
+
[ styles.overlayLabel ]: withOverlayLabel,
|
|
310
|
+
} ) }
|
|
311
|
+
>
|
|
312
|
+
{ isPrimaryVisible && <Text>{ valueFormatter( entry.currentValue ) }</Text> }
|
|
268
313
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
314
|
+
{ withComparison && isComparisonVisible && (
|
|
315
|
+
<Text style={ { color: deltaColor } }>{ deltaFormatter( entry.delta ) }</Text>
|
|
316
|
+
) }
|
|
317
|
+
</div>
|
|
318
|
+
</Fragment>
|
|
319
|
+
);
|
|
320
|
+
} ) }
|
|
321
|
+
</Grid>
|
|
322
|
+
) }
|
|
277
323
|
|
|
278
324
|
{ showLegend && (
|
|
279
325
|
<Legend
|
|
@@ -284,6 +330,7 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
|
|
|
284
330
|
shapeWidth={ legendShapeWidth }
|
|
285
331
|
shapeHeight={ legendShapeHeight }
|
|
286
332
|
chartId={ chartId }
|
|
333
|
+
interactive={ legendInteractive }
|
|
287
334
|
/>
|
|
288
335
|
) }
|
|
289
336
|
|
|
@@ -291,4 +291,52 @@ describe( 'LeaderboardChart', () => {
|
|
|
291
291
|
expect( screen.getByText( '-8%' ) ).toBeInTheDocument();
|
|
292
292
|
} );
|
|
293
293
|
} );
|
|
294
|
+
|
|
295
|
+
describe( 'Interactive Legend', () => {
|
|
296
|
+
it( 'renders legend as interactive when legendInteractive is true', () => {
|
|
297
|
+
render(
|
|
298
|
+
<LeaderboardChart
|
|
299
|
+
data={ mockData }
|
|
300
|
+
withComparison={ true }
|
|
301
|
+
showLegend={ true }
|
|
302
|
+
legendInteractive={ true }
|
|
303
|
+
/>
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const legendItems = screen.getAllByRole( 'button' );
|
|
307
|
+
expect( legendItems.length ).toBeGreaterThan( 0 );
|
|
308
|
+
} );
|
|
309
|
+
|
|
310
|
+
it( 'renders legend as non-interactive when legendInteractive is false', () => {
|
|
311
|
+
render(
|
|
312
|
+
<LeaderboardChart
|
|
313
|
+
data={ mockData }
|
|
314
|
+
withComparison={ true }
|
|
315
|
+
showLegend={ true }
|
|
316
|
+
legendInteractive={ false }
|
|
317
|
+
/>
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// Legend items should not have button role when not interactive
|
|
321
|
+
const legendItems = screen.queryAllByRole( 'button' );
|
|
322
|
+
expect( legendItems ).toHaveLength( 0 );
|
|
323
|
+
} );
|
|
324
|
+
|
|
325
|
+
it( 'shows all data when all series are visible', () => {
|
|
326
|
+
render(
|
|
327
|
+
<LeaderboardChart
|
|
328
|
+
data={ mockData }
|
|
329
|
+
withComparison={ true }
|
|
330
|
+
showLegend={ true }
|
|
331
|
+
legendInteractive={ true }
|
|
332
|
+
/>
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
// All values should be visible
|
|
336
|
+
expect( screen.getByText( '12.5K' ) ).toBeInTheDocument();
|
|
337
|
+
expect( screen.getByText( '8.8K' ) ).toBeInTheDocument();
|
|
338
|
+
expect( screen.getByText( '+25%' ) ).toBeInTheDocument();
|
|
339
|
+
expect( screen.getByText( '-8%' ) ).toBeInTheDocument();
|
|
340
|
+
} );
|
|
341
|
+
} );
|
|
294
342
|
} );
|