@automattic/charts 1.11.0 → 2.0.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 +24 -2
- package/TOKENS.md +19 -1
- package/dist/index.cjs +2196 -2711
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +23 -43
- package/dist/index.d.cts +72 -6
- package/dist/index.d.ts +72 -6
- package/dist/index.js +278 -792
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/charts/area-chart/area-chart.tsx +31 -10
- package/src/charts/area-chart/test/area-chart.test.tsx +102 -0
- package/src/charts/area-chart/types.ts +20 -6
- package/src/charts/bar-chart/bar-chart.tsx +33 -9
- package/src/charts/bar-chart/test/bar-chart.test.tsx +117 -0
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +7 -7
- package/src/charts/line-chart/line-chart.module.scss +3 -3
- package/src/charts/line-chart/line-chart.tsx +43 -7
- package/src/charts/line-chart/test/line-chart.test.tsx +234 -5
- package/src/charts/line-chart/types.ts +14 -0
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +1 -1
- package/src/charts/private/test/time-axis.test.ts +182 -0
- package/src/charts/private/time-axis.ts +84 -5
- package/src/charts/private/x-zoom/index.ts +1 -0
- package/src/charts/private/{test → x-zoom/test}/x-zoom.test.tsx +92 -4
- package/src/charts/private/x-zoom/x-zoom.module.scss +35 -0
- package/src/charts/private/{x-zoom.tsx → x-zoom/x-zoom.tsx} +44 -27
- package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +152 -0
- package/src/components/legend/hooks/use-chart-legend-items.ts +118 -16
- package/src/components/legend/private/base-legend.module.scss +1 -1
- package/src/components/legend/private/base-legend.tsx +21 -9
- package/src/components/legend/test/legend-config-types.test.tsx +32 -0
- package/src/components/legend/test/legend.test.tsx +121 -1
- package/src/components/legend/types.ts +6 -0
- package/src/components/tooltip/base-tooltip.module.scss +1 -1
- package/src/components/trend-indicator/trend-indicator.module.scss +1 -1
- package/src/index.ts +2 -0
- package/src/types.ts +31 -0
- package/src/charts/private/x-zoom.module.scss +0 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/charts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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": {
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
"@visx/xychart": "^4.0.0",
|
|
85
85
|
"@wordpress/i18n": "^6.0.0",
|
|
86
86
|
"@wordpress/icons": "^15.0.0",
|
|
87
|
-
"@wordpress/theme": "
|
|
88
|
-
"@wordpress/ui": "0.
|
|
87
|
+
"@wordpress/theme": "1.1.0",
|
|
88
|
+
"@wordpress/ui": "0.19.0",
|
|
89
89
|
"clsx": "2.1.1",
|
|
90
90
|
"date-fns": "^4.1.0",
|
|
91
91
|
"deepmerge": "4.3.1",
|
|
@@ -105,13 +105,14 @@
|
|
|
105
105
|
"@testing-library/user-event": "^14.4.3",
|
|
106
106
|
"@tsdown/css": "0.22.2",
|
|
107
107
|
"@types/jest": "^30.0.0",
|
|
108
|
+
"@types/node": "^24.12.0",
|
|
108
109
|
"@types/react": "18.3.28",
|
|
109
110
|
"@types/react-dom": "18.3.7",
|
|
110
111
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
111
112
|
"@visx/glyph": "4.0.0",
|
|
112
113
|
"@wordpress/components": "37.0.0",
|
|
113
114
|
"@wordpress/element": "8.3.0",
|
|
114
|
-
"@wordpress/private-apis": "1.
|
|
115
|
+
"@wordpress/private-apis": "1.52.0",
|
|
115
116
|
"babel-jest": "30.4.1",
|
|
116
117
|
"identity-obj-proxy": "^3.0.0",
|
|
117
118
|
"jest": "30.4.2",
|
|
@@ -70,7 +70,8 @@ const AreaChartInternal = forwardRef< SingleChartRef, AreaChartProps >(
|
|
|
70
70
|
onPointerMove,
|
|
71
71
|
onPointerOut,
|
|
72
72
|
zoomable = false,
|
|
73
|
-
|
|
73
|
+
rescaleYOnVisibilityChange,
|
|
74
|
+
rescaleYOnLegendToggle,
|
|
74
75
|
children,
|
|
75
76
|
gridVisibility,
|
|
76
77
|
gap = 'md',
|
|
@@ -81,6 +82,9 @@ const AreaChartInternal = forwardRef< SingleChartRef, AreaChartProps >(
|
|
|
81
82
|
const legendShape = legend.shape ?? 'rect';
|
|
82
83
|
const legendPosition = legend.position ?? 'bottom';
|
|
83
84
|
|
|
85
|
+
// New prop wins; fall back to the deprecated `rescaleYOnLegendToggle`; default to rescaling.
|
|
86
|
+
const rescaleYOnVisibility = rescaleYOnVisibilityChange ?? rescaleYOnLegendToggle ?? true;
|
|
87
|
+
|
|
84
88
|
const providerTheme = useGlobalChartsTheme();
|
|
85
89
|
const theme = useXYChartTheme( data );
|
|
86
90
|
const chartId = useChartId( providedChartId );
|
|
@@ -147,12 +151,12 @@ const AreaChartInternal = forwardRef< SingleChartRef, AreaChartProps >(
|
|
|
147
151
|
// Computed from the full data set (ignoring legend visibility) so the y-axis stays
|
|
148
152
|
// fixed when series are toggled off - otherwise visx auto-fits to the remaining
|
|
149
153
|
// data and the chart's baseline appears to move. Opt-in via
|
|
150
|
-
// `
|
|
154
|
+
// `rescaleYOnVisibilityChange={ false }`. Skipped for non-default stack offsets,
|
|
151
155
|
// which reshape the y-extent (`expand` -> [0,1], `wiggle`/`silhouette` -> centred
|
|
152
156
|
// around zero); letting visx derive the domain is correct there.
|
|
153
157
|
const fixedYDomain = useMemo< [ number, number ] | undefined >( () => {
|
|
154
158
|
if (
|
|
155
|
-
|
|
159
|
+
rescaleYOnVisibility ||
|
|
156
160
|
! legendInteractive ||
|
|
157
161
|
! dataSorted.length ||
|
|
158
162
|
! dataSorted[ 0 ].data.length ||
|
|
@@ -195,10 +199,11 @@ const AreaChartInternal = forwardRef< SingleChartRef, AreaChartProps >(
|
|
|
195
199
|
}
|
|
196
200
|
if ( max === -Infinity ) return undefined;
|
|
197
201
|
return [ Math.min( 0, min ), max ];
|
|
198
|
-
}, [ dataSorted, stacked, stackOffset, legendInteractive,
|
|
202
|
+
}, [ dataSorted, stacked, stackOffset, legendInteractive, rescaleYOnVisibility ] );
|
|
199
203
|
|
|
200
204
|
const chartOptions = useMemo( () => {
|
|
201
|
-
const
|
|
205
|
+
const { tickResolution, ...xAxisOptions } = options?.axis?.x ?? {};
|
|
206
|
+
const formatter = xAxisOptions.tickFormat || getFormatter( dataSorted, tickResolution );
|
|
202
207
|
|
|
203
208
|
return {
|
|
204
209
|
axis: {
|
|
@@ -207,7 +212,7 @@ const AreaChartInternal = forwardRef< SingleChartRef, AreaChartProps >(
|
|
|
207
212
|
numTicks: guessOptimalNumTicks( dataSorted, width, formatter ),
|
|
208
213
|
tickFormat: formatter,
|
|
209
214
|
display: true,
|
|
210
|
-
...
|
|
215
|
+
...xAxisOptions,
|
|
211
216
|
},
|
|
212
217
|
y: {
|
|
213
218
|
orientation: 'left' as const,
|
|
@@ -238,7 +243,14 @@ const AreaChartInternal = forwardRef< SingleChartRef, AreaChartProps >(
|
|
|
238
243
|
const error = validateData( dataSorted );
|
|
239
244
|
const isDataValid = ! error;
|
|
240
245
|
|
|
241
|
-
const legendOptions = useMemo(
|
|
246
|
+
const legendOptions = useMemo(
|
|
247
|
+
() => ( {
|
|
248
|
+
withGlyph: false,
|
|
249
|
+
glyphSize: 0,
|
|
250
|
+
collapseGroups: legend.collapseGroups ?? false,
|
|
251
|
+
} ),
|
|
252
|
+
[ legend.collapseGroups ]
|
|
253
|
+
);
|
|
242
254
|
const legendItems = useChartLegendItems( dataSorted, legendOptions, legendShape );
|
|
243
255
|
|
|
244
256
|
const chartMetadata = useMemo(
|
|
@@ -405,9 +417,18 @@ const AreaChartInternal = forwardRef< SingleChartRef, AreaChartProps >(
|
|
|
405
417
|
onPointerOut={ onPointerOut }
|
|
406
418
|
pointerEventsDataKey="nearest"
|
|
407
419
|
>
|
|
408
|
-
{
|
|
409
|
-
|
|
410
|
-
|
|
420
|
+
{ /* With every series hidden the value scale collapses, so the grid and axes
|
|
421
|
+
are dropped while the empty state stands in — otherwise they render
|
|
422
|
+
squished at the top. */ }
|
|
423
|
+
{ ! allSeriesHidden && gridVisibility !== 'none' && (
|
|
424
|
+
<Grid columns={ false } numTicks={ 4 } />
|
|
425
|
+
) }
|
|
426
|
+
{ ! allSeriesHidden && chartOptions.axis.x.display && (
|
|
427
|
+
<Axis { ...chartOptions.axis.x } />
|
|
428
|
+
) }
|
|
429
|
+
{ ! allSeriesHidden && chartOptions.axis.y.display && (
|
|
430
|
+
<Axis { ...chartOptions.axis.y } />
|
|
431
|
+
) }
|
|
411
432
|
|
|
412
433
|
{ allSeriesHidden ? (
|
|
413
434
|
<SvgEmptyState
|
|
@@ -333,6 +333,35 @@ describe( 'AreaChart', () => {
|
|
|
333
333
|
expect( afterToggleDomain ).toEqual( initialDomain );
|
|
334
334
|
} );
|
|
335
335
|
|
|
336
|
+
test( 'y-axis stays pinned when rescaleYOnVisibilityChange is false', async () => {
|
|
337
|
+
const user = userEvent.setup();
|
|
338
|
+
const ref = createRef< SingleChartRef >();
|
|
339
|
+
render(
|
|
340
|
+
<GlobalChartsProvider>
|
|
341
|
+
<AreaChartUnresponsive
|
|
342
|
+
{ ...defaultProps }
|
|
343
|
+
showLegend
|
|
344
|
+
chartId="test-interactive-domain-pin-new"
|
|
345
|
+
legend={ { interactive: true } }
|
|
346
|
+
rescaleYOnVisibilityChange={ false }
|
|
347
|
+
ref={ ref }
|
|
348
|
+
/>
|
|
349
|
+
</GlobalChartsProvider>
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const initialDomain = (
|
|
353
|
+
ref.current?.getScales()?.yScale as { domain: () => number[] } | undefined
|
|
354
|
+
)?.domain();
|
|
355
|
+
expect( initialDomain ).toBeDefined();
|
|
356
|
+
|
|
357
|
+
await user.click( screen.getByText( 'Series A' ) );
|
|
358
|
+
|
|
359
|
+
const afterToggleDomain = (
|
|
360
|
+
ref.current?.getScales()?.yScale as { domain: () => number[] } | undefined
|
|
361
|
+
)?.domain();
|
|
362
|
+
expect( afterToggleDomain ).toEqual( initialDomain );
|
|
363
|
+
} );
|
|
364
|
+
|
|
336
365
|
test( 'supports negative stacked values without clipping (with pinned Y)', () => {
|
|
337
366
|
// The mixed-sign full-extent pin only kicks in when the consumer
|
|
338
367
|
// opts into pinned-Y behavior; visx's natural domain derivation for
|
|
@@ -544,4 +573,77 @@ describe( 'AreaChart', () => {
|
|
|
544
573
|
'url(#chart-zoom-clip-zoomtest)'
|
|
545
574
|
);
|
|
546
575
|
} );
|
|
576
|
+
|
|
577
|
+
describe( 'Legend group collapsing', () => {
|
|
578
|
+
const groupedData = [
|
|
579
|
+
{
|
|
580
|
+
label: 'Views',
|
|
581
|
+
group: 'views',
|
|
582
|
+
data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
label: 'Views — previous',
|
|
586
|
+
group: 'views',
|
|
587
|
+
options: { type: 'comparison' as const },
|
|
588
|
+
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
589
|
+
},
|
|
590
|
+
];
|
|
591
|
+
|
|
592
|
+
test( 'renders one legend item per series by default', () => {
|
|
593
|
+
renderWithProvider( { showLegend: true, data: groupedData } );
|
|
594
|
+
|
|
595
|
+
expect( screen.getByText( 'Views' ) ).toBeInTheDocument();
|
|
596
|
+
expect( screen.getByText( 'Views — previous' ) ).toBeInTheDocument();
|
|
597
|
+
} );
|
|
598
|
+
|
|
599
|
+
test( 'collapses a group to one item when legend.collapseGroups is set', () => {
|
|
600
|
+
renderWithProvider( {
|
|
601
|
+
showLegend: true,
|
|
602
|
+
legend: { collapseGroups: true },
|
|
603
|
+
data: groupedData,
|
|
604
|
+
} );
|
|
605
|
+
|
|
606
|
+
expect( screen.getByText( 'Views' ) ).toBeInTheDocument();
|
|
607
|
+
expect( screen.queryByText( 'Views — previous' ) ).not.toBeInTheDocument();
|
|
608
|
+
} );
|
|
609
|
+
} );
|
|
610
|
+
|
|
611
|
+
describe( 'All series hidden', () => {
|
|
612
|
+
it( 'drops the grid so it does not collapse when every series is hidden', async () => {
|
|
613
|
+
const user = userEvent.setup();
|
|
614
|
+
|
|
615
|
+
renderWithProvider( {
|
|
616
|
+
showLegend: true,
|
|
617
|
+
legend: { interactive: true },
|
|
618
|
+
chartId: 'area-hidden-axes',
|
|
619
|
+
data: [
|
|
620
|
+
{
|
|
621
|
+
label: 'Series A',
|
|
622
|
+
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
label: 'Series B',
|
|
626
|
+
data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
|
|
627
|
+
},
|
|
628
|
+
],
|
|
629
|
+
} );
|
|
630
|
+
|
|
631
|
+
// The visx grid has no role/testid, so query its internal class from within the plot.
|
|
632
|
+
/* eslint-disable testing-library/no-node-access */
|
|
633
|
+
const plot = screen.getByRole( 'grid' );
|
|
634
|
+
expect( plot.querySelector( '.visx-rows' ) ).toBeInTheDocument();
|
|
635
|
+
|
|
636
|
+
const buttons = screen.getAllByRole( 'button' );
|
|
637
|
+
await user.click( buttons[ 0 ] );
|
|
638
|
+
await user.click( buttons[ 1 ] );
|
|
639
|
+
|
|
640
|
+
// With no visible data the value scale collapses, so the grid is removed rather than
|
|
641
|
+
// rendered squished at the top behind the empty state.
|
|
642
|
+
expect( plot.querySelector( '.visx-rows' ) ).not.toBeInTheDocument();
|
|
643
|
+
/* eslint-enable testing-library/no-node-access */
|
|
644
|
+
expect(
|
|
645
|
+
screen.getByText( /all series are hidden.*click legend items to show data/i )
|
|
646
|
+
).toBeInTheDocument();
|
|
647
|
+
} );
|
|
648
|
+
} );
|
|
547
649
|
} );
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
BaseChartProps,
|
|
3
|
+
DataPointDate,
|
|
4
|
+
SeriesData,
|
|
5
|
+
SeriesChartLegendConfig,
|
|
6
|
+
} from '../../types';
|
|
2
7
|
import type { RenderTooltipParams } from '../../visx/types';
|
|
3
8
|
import type { CurveType } from '../line-chart/types';
|
|
4
9
|
import type { ReactNode } from 'react';
|
|
5
10
|
|
|
6
11
|
export interface AreaChartProps extends BaseChartProps< SeriesData[] > {
|
|
12
|
+
/**
|
|
13
|
+
* Legend configuration. Supports `collapseGroups` on top of the shared options.
|
|
14
|
+
*/
|
|
15
|
+
legend?: SeriesChartLegendConfig;
|
|
7
16
|
/**
|
|
8
17
|
* Whether series should be stacked on top of each other.
|
|
9
18
|
* When false, series are rendered as overlapping filled areas.
|
|
@@ -54,13 +63,18 @@ export interface AreaChartProps extends BaseChartProps< SeriesData[] > {
|
|
|
54
63
|
*/
|
|
55
64
|
zoomable?: boolean;
|
|
56
65
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* the
|
|
61
|
-
* not move the chart's baseline.
|
|
66
|
+
* Whether the Y axis rescales to fit only the visible series when series are
|
|
67
|
+
* hidden or shown through the interactive legend.
|
|
68
|
+
* Defaults to `true`, matching LineChart. Set to `false` to pin the Y axis to
|
|
69
|
+
* the full data extent so hiding series does not move the chart's baseline.
|
|
62
70
|
* @default true
|
|
63
71
|
*/
|
|
72
|
+
rescaleYOnVisibilityChange?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* @deprecated Use `rescaleYOnVisibilityChange`. The behaviour keys off series
|
|
75
|
+
* visibility changing, not specifically a legend toggle. Still honoured when
|
|
76
|
+
* `rescaleYOnVisibilityChange` is not set.
|
|
77
|
+
*/
|
|
64
78
|
rescaleYOnLegendToggle?: boolean;
|
|
65
79
|
children?: ReactNode;
|
|
66
80
|
}
|
|
@@ -37,12 +37,22 @@ import {
|
|
|
37
37
|
BASE_BAND_PADDING_INNER,
|
|
38
38
|
} from './private';
|
|
39
39
|
import type { ComparisonSeriesEntry } from './private';
|
|
40
|
-
import type {
|
|
40
|
+
import type {
|
|
41
|
+
BaseChartProps,
|
|
42
|
+
DataPointDate,
|
|
43
|
+
SeriesData,
|
|
44
|
+
SeriesChartLegendConfig,
|
|
45
|
+
Optional,
|
|
46
|
+
} from '../../types';
|
|
41
47
|
import type { RenderTooltipParams } from '../../visx/types';
|
|
42
48
|
import type { ResponsiveConfig } from '../private/with-responsive';
|
|
43
49
|
import type { FC, ReactNode, ComponentType } from 'react';
|
|
44
50
|
|
|
45
51
|
export interface BarChartProps extends BaseChartProps< SeriesData[] > {
|
|
52
|
+
/**
|
|
53
|
+
* Legend configuration. Supports `collapseGroups` on top of the shared options.
|
|
54
|
+
*/
|
|
55
|
+
legend?: SeriesChartLegendConfig;
|
|
46
56
|
renderTooltip?: ( params: RenderTooltipParams< DataPointDate > ) => ReactNode;
|
|
47
57
|
orientation?: 'horizontal' | 'vertical';
|
|
48
58
|
withPatterns?: boolean;
|
|
@@ -117,6 +127,7 @@ const BarChartInternal: FC< BarChartProps > = ( {
|
|
|
117
127
|
gap = 'md',
|
|
118
128
|
} ) => {
|
|
119
129
|
const legendInteractive = legend.interactive ?? false;
|
|
130
|
+
const legendCollapseGroups = legend.collapseGroups ?? false;
|
|
120
131
|
const horizontal = orientation === 'horizontal';
|
|
121
132
|
const chartId = useChartId( providedChartId );
|
|
122
133
|
const theme = useXYChartTheme( data );
|
|
@@ -131,7 +142,11 @@ const BarChartInternal: FC< BarChartProps > = ( {
|
|
|
131
142
|
} );
|
|
132
143
|
|
|
133
144
|
// Create legend items using the reusable hook
|
|
134
|
-
const
|
|
145
|
+
const legendOptions = useMemo(
|
|
146
|
+
() => ( { collapseGroups: legendCollapseGroups } ),
|
|
147
|
+
[ legendCollapseGroups ]
|
|
148
|
+
);
|
|
149
|
+
const legendItems = useChartLegendItems( dataSorted, legendOptions );
|
|
135
150
|
const chartOptions = useBarChartOptions( dataWithVisibleZeros, horizontal, options );
|
|
136
151
|
const defaultMargin = useChartMargin( height, chartOptions, dataSorted, theme, horizontal );
|
|
137
152
|
const chartRef = useRef< HTMLDivElement >( null );
|
|
@@ -544,11 +559,13 @@ const BarChartInternal: FC< BarChartProps > = ( {
|
|
|
544
559
|
horizontal={ horizontal }
|
|
545
560
|
pointerEventsDataKey="nearest"
|
|
546
561
|
>
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
562
|
+
{ ! allSeriesHidden && (
|
|
563
|
+
<Grid
|
|
564
|
+
columns={ gridVisibility.includes( 'y' ) }
|
|
565
|
+
rows={ gridVisibility.includes( 'x' ) }
|
|
566
|
+
numTicks={ 4 }
|
|
567
|
+
/>
|
|
568
|
+
) }
|
|
552
569
|
|
|
553
570
|
{ withPatterns && (
|
|
554
571
|
<>
|
|
@@ -615,8 +632,15 @@ const BarChartInternal: FC< BarChartProps > = ( {
|
|
|
615
632
|
) ) }
|
|
616
633
|
</BarGroup>
|
|
617
634
|
|
|
618
|
-
|
|
619
|
-
|
|
635
|
+
{ /* With every series hidden there is no data to build the value scale from, so
|
|
636
|
+
visx collapses the domain and the axes render squished at the top. Drop them
|
|
637
|
+
while the empty state stands in. */ }
|
|
638
|
+
{ ! allSeriesHidden && (
|
|
639
|
+
<>
|
|
640
|
+
<Axis { ...chartOptions.axis.x } />
|
|
641
|
+
<Axis { ...chartOptions.axis.y } />
|
|
642
|
+
</>
|
|
643
|
+
) }
|
|
620
644
|
|
|
621
645
|
{ withTooltips && (
|
|
622
646
|
<AccessibleTooltip
|
|
@@ -1194,5 +1194,122 @@ describe( 'BarChart', () => {
|
|
|
1194
1194
|
screen.getByText( /all series are hidden.*click legend items to show data/i )
|
|
1195
1195
|
).toBeInTheDocument();
|
|
1196
1196
|
} );
|
|
1197
|
+
|
|
1198
|
+
it( 'drops the value-axis ticks when all series are hidden so the axes do not collapse', async () => {
|
|
1199
|
+
const user = userEvent.setup();
|
|
1200
|
+
|
|
1201
|
+
renderWithTheme( {
|
|
1202
|
+
showLegend: true,
|
|
1203
|
+
gridVisibility: 'both',
|
|
1204
|
+
legend: { interactive: true },
|
|
1205
|
+
chartId: 'test-hidden-axes-bar-chart',
|
|
1206
|
+
data: [
|
|
1207
|
+
{
|
|
1208
|
+
label: 'Series A',
|
|
1209
|
+
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
1210
|
+
options: {},
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
label: 'Series B',
|
|
1214
|
+
data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
|
|
1215
|
+
options: {},
|
|
1216
|
+
},
|
|
1217
|
+
],
|
|
1218
|
+
} );
|
|
1219
|
+
|
|
1220
|
+
// The value axis renders numeric tick labels while there is data to scale against.
|
|
1221
|
+
// (Series labels carry no digits and the empty-state message is text-only, so a bare
|
|
1222
|
+
// number can only be an axis tick.)
|
|
1223
|
+
const numericTick = /^[\d,]+$/;
|
|
1224
|
+
expect( screen.getAllByText( numericTick ).length ).toBeGreaterThan( 0 );
|
|
1225
|
+
|
|
1226
|
+
const legendItems = screen.getAllByRole( 'button' );
|
|
1227
|
+
await user.click( legendItems[ 0 ] );
|
|
1228
|
+
await user.click( legendItems[ 1 ] );
|
|
1229
|
+
|
|
1230
|
+
// With no visible data the value scale would collapse, so the axes are removed rather
|
|
1231
|
+
// than rendered squished at the top — no tick labels remain.
|
|
1232
|
+
expect( screen.queryAllByText( numericTick ) ).toHaveLength( 0 );
|
|
1233
|
+
expect(
|
|
1234
|
+
screen.getByText( /all series are hidden.*click legend items to show data/i )
|
|
1235
|
+
).toBeInTheDocument();
|
|
1236
|
+
} );
|
|
1237
|
+
} );
|
|
1238
|
+
|
|
1239
|
+
describe( 'Legend group collapsing', () => {
|
|
1240
|
+
const comparisonPair = [
|
|
1241
|
+
{
|
|
1242
|
+
label: 'Views',
|
|
1243
|
+
group: 'views',
|
|
1244
|
+
data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
label: 'Views — previous',
|
|
1248
|
+
group: 'views',
|
|
1249
|
+
options: { type: 'comparison' as const },
|
|
1250
|
+
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
1251
|
+
},
|
|
1252
|
+
];
|
|
1253
|
+
|
|
1254
|
+
it( 'renders one legend item per series by default', () => {
|
|
1255
|
+
renderWithTheme( {
|
|
1256
|
+
showLegend: true,
|
|
1257
|
+
chartId: 'bar-legend-groups-default',
|
|
1258
|
+
data: comparisonPair,
|
|
1259
|
+
} );
|
|
1260
|
+
|
|
1261
|
+
expect( screen.getByText( 'Views' ) ).toBeInTheDocument();
|
|
1262
|
+
expect( screen.getByText( 'Views — previous' ) ).toBeInTheDocument();
|
|
1263
|
+
} );
|
|
1264
|
+
|
|
1265
|
+
it( 'collapses a group to one item when legend.collapseGroups is set', () => {
|
|
1266
|
+
renderWithTheme( {
|
|
1267
|
+
showLegend: true,
|
|
1268
|
+
legend: { collapseGroups: true },
|
|
1269
|
+
chartId: 'bar-legend-groups-collapsed',
|
|
1270
|
+
data: comparisonPair,
|
|
1271
|
+
} );
|
|
1272
|
+
|
|
1273
|
+
expect( screen.getByText( 'Views' ) ).toBeInTheDocument();
|
|
1274
|
+
expect( screen.queryByText( 'Views — previous' ) ).not.toBeInTheDocument();
|
|
1275
|
+
} );
|
|
1276
|
+
|
|
1277
|
+
it( 'toggles only the clicked series when interactive without collapseGroups', async () => {
|
|
1278
|
+
const user = userEvent.setup();
|
|
1279
|
+
|
|
1280
|
+
renderWithTheme( {
|
|
1281
|
+
showLegend: true,
|
|
1282
|
+
legend: { interactive: true },
|
|
1283
|
+
chartId: 'bar-legend-groups-interactive',
|
|
1284
|
+
data: comparisonPair,
|
|
1285
|
+
} );
|
|
1286
|
+
|
|
1287
|
+
const buttons = screen.getAllByRole( 'button' );
|
|
1288
|
+
await user.click( buttons[ 0 ] );
|
|
1289
|
+
|
|
1290
|
+
expect( buttons[ 0 ] ).toHaveAttribute( 'aria-pressed', 'false' );
|
|
1291
|
+
expect( buttons[ 1 ] ).toHaveAttribute( 'aria-pressed', 'true' );
|
|
1292
|
+
} );
|
|
1293
|
+
|
|
1294
|
+
it( 'toggles the whole group when interactive with collapseGroups', async () => {
|
|
1295
|
+
const user = userEvent.setup();
|
|
1296
|
+
|
|
1297
|
+
renderWithTheme( {
|
|
1298
|
+
showLegend: true,
|
|
1299
|
+
legend: { interactive: true, collapseGroups: true },
|
|
1300
|
+
chartId: 'bar-legend-groups-interactive-collapsed',
|
|
1301
|
+
data: comparisonPair,
|
|
1302
|
+
} );
|
|
1303
|
+
|
|
1304
|
+
const buttons = screen.getAllByRole( 'button' );
|
|
1305
|
+
expect( buttons ).toHaveLength( 1 );
|
|
1306
|
+
|
|
1307
|
+
await user.click( buttons[ 0 ] );
|
|
1308
|
+
|
|
1309
|
+
expect( buttons[ 0 ] ).toHaveAttribute( 'aria-pressed', 'false' );
|
|
1310
|
+
expect(
|
|
1311
|
+
screen.getByText( /all series are hidden.*click legend items to show data/i )
|
|
1312
|
+
).toBeInTheDocument();
|
|
1313
|
+
} );
|
|
1197
1314
|
} );
|
|
1198
1315
|
} );
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
text-overflow: ellipsis;
|
|
17
17
|
font-size: var(--wpds-typography-font-size-xl, 18px);
|
|
18
18
|
font-style: normal;
|
|
19
|
-
font-weight: var(--wpds-typography-font-weight-
|
|
19
|
+
font-weight: var(--wpds-typography-font-weight-emphasis, 499);
|
|
20
20
|
line-height: var(--wpds-typography-line-height-sm, 20px);
|
|
21
21
|
margin: 0;
|
|
22
22
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
text-overflow: ellipsis;
|
|
27
27
|
font-size: var(--wpds-typography-font-size-md, 13px);
|
|
28
28
|
font-style: normal;
|
|
29
|
-
font-weight: var(--wpds-typography-font-weight-
|
|
29
|
+
font-weight: var(--wpds-typography-font-weight-emphasis, 499);
|
|
30
30
|
line-height: var(--wpds-typography-line-height-sm, 20px);
|
|
31
31
|
margin: 0;
|
|
32
32
|
}
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
.step-label {
|
|
57
57
|
color: var(--a8c-charts-color-label-secondary, var(--wpds-color-foreground-content-neutral-weak, #707070));
|
|
58
58
|
font-size: var(--wpds-typography-font-size-sm, 12px);
|
|
59
|
-
font-weight: var(--wpds-typography-font-weight-
|
|
59
|
+
font-weight: var(--wpds-typography-font-weight-default, 400);
|
|
60
60
|
line-height: var(--wpds-typography-line-height-xs, 16px);
|
|
61
61
|
white-space: nowrap;
|
|
62
62
|
overflow: hidden;
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
.step-rate {
|
|
67
67
|
color: var(--a8c-charts-color-label, var(--wpds-color-foreground-content-neutral, #1e1e1e));
|
|
68
68
|
font-size: var(--wpds-typography-font-size-md, 13px);
|
|
69
|
-
font-weight: var(--wpds-typography-font-weight-
|
|
69
|
+
font-weight: var(--wpds-typography-font-weight-emphasis, 499);
|
|
70
70
|
line-height: var(--wpds-typography-line-height-xs, 16px);
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
// Override .visx-tooltip inline styles.
|
|
106
106
|
border-radius: var(--wpds-border-radius-md, 4px) !important;
|
|
107
107
|
padding: var(--wpds-dimension-padding-md, 12px) !important;
|
|
108
|
-
box-shadow: var(--
|
|
108
|
+
box-shadow: var(--a8c-charts-elevation-sm, 0 1px 2px 0 #0000000d, 0 2px 3px 0 #0000000a, 0 6px 6px 0 #00000008, 0 8px 8px 0 #00000005) !important;
|
|
109
109
|
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
color: var(--a8c-charts-color-label, var(--wpds-color-foreground-content-neutral, #1e1e1e));
|
|
114
114
|
font-size: var(--wpds-typography-font-size-sm, 12px);
|
|
115
115
|
font-style: normal;
|
|
116
|
-
font-weight: var(--wpds-typography-font-weight-
|
|
116
|
+
font-weight: var(--wpds-typography-font-weight-default, 400);
|
|
117
117
|
line-height: var(--wpds-typography-line-height-xs, 16px);
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
color: var(--a8c-charts-color-label, var(--wpds-color-foreground-content-neutral, #1e1e1e));
|
|
122
122
|
font-size: var(--wpds-typography-font-size-md, 13px);
|
|
123
123
|
font-style: normal;
|
|
124
|
-
font-weight: var(--wpds-typography-font-weight-
|
|
124
|
+
font-weight: var(--wpds-typography-font-weight-emphasis, 499);
|
|
125
125
|
line-height: var(--wpds-typography-line-height-sm, 20px);
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
&__tooltip-date {
|
|
27
|
-
font-weight: var(--wpds-typography-font-weight-
|
|
27
|
+
font-weight: var(--wpds-typography-font-weight-emphasis, 499);
|
|
28
28
|
padding-bottom: var(--wpds-dimension-padding-md, 12px);
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
&__tooltip-label {
|
|
36
|
-
font-weight: var(--wpds-typography-font-weight-
|
|
36
|
+
font-weight: var(--wpds-typography-font-weight-emphasis, 499);
|
|
37
37
|
padding-right: var(--wpds-dimension-padding-lg, 16px);
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
border: none;
|
|
66
66
|
border-radius: var(--wpds-border-radius-md, 4px);
|
|
67
67
|
font-size: var(--wpds-typography-font-size-md, 13px);
|
|
68
|
-
box-shadow: var(--
|
|
68
|
+
box-shadow: var(--a8c-charts-elevation-sm, 0 1px 2px 0 #0000000d, 0 2px 3px 0 #0000000a, 0 6px 6px 0 #00000008, 0 8px 8px 0 #00000005);
|
|
69
69
|
position: fixed;
|
|
70
70
|
// Without margin set, the popover has margin:auto which upsets the
|
|
71
71
|
// positioning relative to the trigger button. A gap token is appropriate
|