@automattic/charts 4.1.0 → 4.2.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 +28 -0
- package/TOKENS.md +7 -5
- package/dist/index.cjs +1115 -471
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +39 -1
- package/dist/index.d.cts +116 -16
- package/dist/index.d.ts +116 -16
- package/dist/index.js +1115 -473
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/charts/area-chart/area-chart.tsx +2 -1
- package/src/charts/area-chart/private/overlays.tsx +1 -2
- package/src/charts/area-chart/test/area-chart.test.tsx +31 -0
- package/src/charts/bar-chart/bar-chart.tsx +67 -17
- package/src/charts/bar-chart/index.ts +1 -1
- package/src/charts/bar-chart/private/band-highlight.tsx +85 -0
- package/src/charts/bar-chart/private/band-scale.ts +13 -0
- package/src/charts/bar-chart/private/band-tooltip.tsx +138 -0
- package/src/charts/bar-chart/private/comparison-bars.tsx +5 -10
- package/src/charts/bar-chart/private/index.ts +2 -0
- package/src/charts/bar-chart/private/test/band-highlight.test.tsx +109 -0
- package/src/charts/bar-chart/private/test/band-tooltip.test.ts +26 -0
- package/src/charts/bar-chart/private/truncated-tick-component.tsx +1 -1
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +1 -1
- package/src/charts/bar-chart/test/bar-chart-pointer-events.test.tsx +11 -23
- package/src/charts/bar-chart/test/bar-chart.test.tsx +565 -1
- package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -3
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +1 -1
- package/src/charts/conversion-funnel-chart/types.ts +4 -2
- package/src/charts/geo-chart/types.ts +4 -2
- package/src/charts/heatmap-chart/{private/build-calendar-data.ts → build-calendar-data.ts} +15 -45
- package/src/charts/heatmap-chart/build-month-calendar-data.ts +124 -0
- package/src/charts/heatmap-chart/heatmap-chart.module.scss +32 -0
- package/src/charts/heatmap-chart/heatmap-chart.tsx +191 -114
- package/src/charts/heatmap-chart/index.ts +15 -3
- package/src/charts/heatmap-chart/private/civil-day.ts +26 -0
- package/src/charts/heatmap-chart/private/column-groups.ts +71 -0
- package/src/charts/heatmap-chart/private/heatmap-legend.tsx +19 -3
- package/src/charts/heatmap-chart/private/index.ts +2 -2
- package/src/charts/heatmap-chart/private/keyboard-navigation.ts +201 -0
- package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +37 -1
- package/src/charts/heatmap-chart/test/build-month-calendar-data.test.ts +194 -0
- package/src/charts/heatmap-chart/test/column-groups.test.ts +83 -0
- package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +428 -2
- package/src/charts/heatmap-chart/test/keyboard-navigation.test.ts +25 -0
- package/src/charts/heatmap-chart/test/use-calendar-heatmap-data.test.tsx +1 -1
- package/src/charts/heatmap-chart/test/use-month-calendar-heatmap-data.test.tsx +90 -0
- package/src/charts/heatmap-chart/test/zoned-calendar-data.test.ts +1 -1
- package/src/charts/heatmap-chart/test/zoned-month-calendar-data.test.ts +23 -0
- package/src/charts/heatmap-chart/types.ts +88 -3
- package/src/charts/heatmap-chart/use-calendar-heatmap-data.ts +2 -2
- package/src/charts/heatmap-chart/use-month-calendar-heatmap-data.ts +34 -0
- package/src/charts/leaderboard-chart/types.ts +13 -14
- package/src/charts/line-chart/line-chart.tsx +3 -2
- package/src/charts/line-chart/private/line-chart-annotation.tsx +1 -1
- package/src/charts/line-chart/test/line-chart.test.tsx +31 -0
- package/src/charts/line-chart/types.ts +2 -2
- package/src/charts/private/time-axis-options.ts +1 -1
- package/src/charts/private/with-responsive/with-responsive.tsx +2 -2
- package/src/charts/private/x-zoom/test/x-zoom.test.tsx +1 -3
- package/src/charts/private/x-zoom/x-zoom.tsx +1 -2
- package/src/components/legend/private/base-legend.tsx +1 -1
- package/src/components/tooltip/accessible-tooltip.tsx +146 -37
- package/src/components/tooltip/base-tooltip.module.scss +6 -1
- package/src/components/tooltip/private/bounded-tooltip.tsx +12 -6
- package/src/components/tooltip/test/accessible-tooltip.test.tsx +160 -0
- package/src/components/tooltip/test/bounded-tooltip.test.tsx +17 -0
- package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +29 -3
- package/src/components/tooltip/xy-chart-tooltip.tsx +11 -11
- package/src/hooks/test/use-element-size.test.tsx +13 -13
- package/src/hooks/use-xychart-theme.ts +1 -1
- package/src/index.ts +7 -1
- package/src/providers/chart-context/themes.ts +1 -0
- package/src/styles/chart-scope.scss +11 -3
- package/src/styles/test/chart-scope.test.ts +36 -2
- package/src/types.ts +6 -13
- package/src/utils/format-metric-value.ts +4 -4
- package/src/utils/get-edge-tick-widths.ts +1 -1
- package/src/utils/get-styles.ts +1 -1
- package/src/visx/types.ts +7 -10
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { scaleBand } from '@visx/scale';
|
|
4
|
+
import { DataContext, TooltipContext } from '@visx/xychart';
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
import { GlobalChartsProvider } from '../../../../providers';
|
|
7
|
+
import { CATALOG_POINTERS } from '../../../../providers/chart-context/private/catalog-pointers';
|
|
8
|
+
import { BarChartUnresponsive } from '../../bar-chart';
|
|
9
|
+
import { BandHighlight } from '../band-highlight';
|
|
10
|
+
import type { BandHighlightSelection } from '../../bar-chart';
|
|
11
|
+
|
|
12
|
+
const xScale = scaleBand( { domain: [ 'Jan', 'Feb' ], range: [ 10, 210 ] } );
|
|
13
|
+
const dataContext = {
|
|
14
|
+
xScale,
|
|
15
|
+
dataRegistry: { get: () => ( { xAccessor: datum => datum.label } ) },
|
|
16
|
+
margin: { top: 20, left: 10 },
|
|
17
|
+
innerWidth: 200,
|
|
18
|
+
innerHeight: 150,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const Fixture = ( { label = 'Jan', onChange } ) => {
|
|
22
|
+
return (
|
|
23
|
+
<DataContext.Provider value={ dataContext as never }>
|
|
24
|
+
<TooltipContext.Provider
|
|
25
|
+
value={
|
|
26
|
+
{
|
|
27
|
+
tooltipOpen: true,
|
|
28
|
+
tooltipData: { nearestDatum: { key: 'scores', datum: { label, value: 50 } } },
|
|
29
|
+
} as never
|
|
30
|
+
}
|
|
31
|
+
>
|
|
32
|
+
<svg>
|
|
33
|
+
<BandHighlight visible horizontal={ false } onChange={ onChange } />
|
|
34
|
+
</svg>
|
|
35
|
+
</TooltipContext.Provider>
|
|
36
|
+
</DataContext.Provider>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
describe( 'BandHighlight', () => {
|
|
41
|
+
test( 'uses the catalog surface fill', () => {
|
|
42
|
+
render( <Fixture onChange={ undefined } /> );
|
|
43
|
+
expect( screen.getByTestId( 'bar-chart-band-highlight' ) ).toHaveAttribute(
|
|
44
|
+
'fill',
|
|
45
|
+
CATALOG_POINTERS.surfaceSecondary
|
|
46
|
+
);
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
test( 'settles inline derived-state callbacks and notifies the latest callback on selection changes', () => {
|
|
50
|
+
const notify = jest.fn();
|
|
51
|
+
const DerivedState = ( { label } ) => {
|
|
52
|
+
const [ band, setBand ] = useState< { x: number } | null >( null );
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
<output>{ band?.x }</output>
|
|
56
|
+
<Fixture
|
|
57
|
+
label={ label }
|
|
58
|
+
// eslint-disable-next-line react/jsx-no-bind -- An inline callback reproduces parent state updates changing its identity.
|
|
59
|
+
onChange={ ( selection: BandHighlightSelection | null ) => {
|
|
60
|
+
notify( label );
|
|
61
|
+
if ( notify.mock.calls.length > 4 ) {
|
|
62
|
+
throw new Error( 'Highlight notifications did not settle' );
|
|
63
|
+
}
|
|
64
|
+
setBand( selection && { x: selection.x } );
|
|
65
|
+
} }
|
|
66
|
+
/>
|
|
67
|
+
</>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
const { rerender } = render( <DerivedState label="Jan" /> );
|
|
71
|
+
expect( screen.getByRole( 'status' ) ).toHaveTextContent( '10' );
|
|
72
|
+
expect( notify ).toHaveBeenCalledTimes( 1 );
|
|
73
|
+
rerender( <DerivedState label="Feb" /> );
|
|
74
|
+
expect( screen.getByRole( 'status' ) ).toHaveTextContent( '110' );
|
|
75
|
+
expect( notify ).toHaveBeenCalledTimes( 2 );
|
|
76
|
+
expect( notify ).toHaveBeenLastCalledWith( 'Feb' );
|
|
77
|
+
} );
|
|
78
|
+
|
|
79
|
+
test.each( [ { withTooltips: false }, { data: [] } ] )(
|
|
80
|
+
'clears the selected band when the highlight unmounts with %j',
|
|
81
|
+
async props => {
|
|
82
|
+
const user = userEvent.setup();
|
|
83
|
+
const onChange = jest.fn();
|
|
84
|
+
const chartProps = {
|
|
85
|
+
width: 500,
|
|
86
|
+
height: 300,
|
|
87
|
+
withTooltips: true,
|
|
88
|
+
onBandHighlightChange: onChange,
|
|
89
|
+
data: [ { label: 'Scores', data: [ { label: 'Jan', value: 50 } ] } ],
|
|
90
|
+
};
|
|
91
|
+
const { rerender } = render(
|
|
92
|
+
<GlobalChartsProvider>
|
|
93
|
+
<BarChartUnresponsive { ...chartProps } />
|
|
94
|
+
</GlobalChartsProvider>
|
|
95
|
+
);
|
|
96
|
+
await user.tab();
|
|
97
|
+
await user.keyboard( '{ArrowRight}' );
|
|
98
|
+
expect( onChange ).toHaveBeenLastCalledWith(
|
|
99
|
+
expect.objectContaining( { datum: expect.objectContaining( { label: 'Jan' } ) } )
|
|
100
|
+
);
|
|
101
|
+
rerender(
|
|
102
|
+
<GlobalChartsProvider>
|
|
103
|
+
<BarChartUnresponsive { ...chartProps } { ...props } />
|
|
104
|
+
</GlobalChartsProvider>
|
|
105
|
+
);
|
|
106
|
+
expect( onChange ).toHaveBeenLastCalledWith( null );
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
} );
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { scaleBand } from '@visx/scale';
|
|
2
|
+
import { nearestBandIndex } from '../band-tooltip';
|
|
3
|
+
|
|
4
|
+
describe( 'nearestBandIndex', () => {
|
|
5
|
+
test.each( [
|
|
6
|
+
[ 25, 688 ],
|
|
7
|
+
[ 688, 25 ],
|
|
8
|
+
] )( 'matches every painted center with outer padding in range %s to %s', ( start, end ) => {
|
|
9
|
+
const data = Array.from( { length: 30 }, ( _, index ) => String( index ) );
|
|
10
|
+
const scale = scaleBand( {
|
|
11
|
+
domain: data,
|
|
12
|
+
range: [ start, end ],
|
|
13
|
+
paddingInner: 0.024,
|
|
14
|
+
paddingOuter: 0.7,
|
|
15
|
+
} );
|
|
16
|
+
data.forEach( ( datum, index ) => {
|
|
17
|
+
expect(
|
|
18
|
+
nearestBandIndex( data, value => value, scale, scale( datum )! + scale.bandwidth() / 2 )
|
|
19
|
+
).toBe( index );
|
|
20
|
+
} );
|
|
21
|
+
} );
|
|
22
|
+
test( 'ignores data outside the band domain', () => {
|
|
23
|
+
const scale = scaleBand( { domain: [ 'recorded' ], range: [ 0, 100 ] } );
|
|
24
|
+
expect( nearestBandIndex( [ 'missing' ], value => value, scale, 50 ) ).toBe( -1 );
|
|
25
|
+
} );
|
|
26
|
+
} );
|
|
@@ -11,7 +11,7 @@ import type { FC, CSSProperties } from 'react';
|
|
|
11
11
|
* @return The bandwidth of the scale
|
|
12
12
|
*/
|
|
13
13
|
const getScaleBandwidth = < Scale extends AxisScale >( scale?: Scale ) => {
|
|
14
|
-
return scale && 'bandwidth' in scale ? scale.bandwidth() ?? 0 : 0;
|
|
14
|
+
return scale && 'bandwidth' in scale ? ( scale.bandwidth() ?? 0 ) : 0;
|
|
15
15
|
};
|
|
16
16
|
interface TruncatedTickComponentProps extends TickRendererProps {
|
|
17
17
|
/** Which axis this tick belongs to */
|
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
import { render } from '@testing-library/react';
|
|
2
2
|
import { GlobalChartsProvider } from '../../../providers';
|
|
3
3
|
import BarChart from '../bar-chart';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
jest.mock( '@visx/xychart', () => {
|
|
12
|
-
const actual = jest.requireActual( '@visx/xychart' );
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
__esModule: true,
|
|
16
|
-
...actual,
|
|
17
|
-
XYChart: ( props: { children?: ReactNode } ) => {
|
|
18
|
-
mockXYChart( props );
|
|
19
|
-
return <svg />;
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
} );
|
|
4
|
+
const mockBandTooltip = jest.fn();
|
|
5
|
+
jest.mock( '../private/band-tooltip', () => ( {
|
|
6
|
+
BandTooltip: props => {
|
|
7
|
+
mockBandTooltip( props );
|
|
8
|
+
return null;
|
|
9
|
+
},
|
|
10
|
+
} ) );
|
|
23
11
|
|
|
24
12
|
const mockRefCallback = jest.fn();
|
|
25
13
|
jest.mock( '../../../hooks/use-element-size', () => ( {
|
|
@@ -38,7 +26,7 @@ const DATA = [
|
|
|
38
26
|
];
|
|
39
27
|
|
|
40
28
|
/**
|
|
41
|
-
* Render the chart with the recorder standing in for
|
|
29
|
+
* Render the chart with the recorder standing in for band pointer handling.
|
|
42
30
|
*
|
|
43
31
|
* @param props - Props layered onto the defaults.
|
|
44
32
|
* @return The render result.
|
|
@@ -52,15 +40,15 @@ function renderBarChart( props = {} ) {
|
|
|
52
40
|
}
|
|
53
41
|
|
|
54
42
|
describe( 'BarChart pointer events', () => {
|
|
55
|
-
beforeEach( () =>
|
|
43
|
+
beforeEach( () => mockBandTooltip.mockClear() );
|
|
56
44
|
|
|
57
|
-
test( '
|
|
45
|
+
test( 'routes pointer handlers through band correction without requiring tooltips', () => {
|
|
58
46
|
const onPointerDown = jest.fn();
|
|
59
47
|
const onPointerUp = jest.fn();
|
|
60
48
|
|
|
61
49
|
renderBarChart( { onPointerDown, onPointerUp } );
|
|
62
50
|
|
|
63
|
-
const props =
|
|
51
|
+
const props = mockBandTooltip.mock.calls.at( -1 )[ 0 ];
|
|
64
52
|
expect( props.onPointerDown ).toBe( onPointerDown );
|
|
65
53
|
expect( props.onPointerUp ).toBe( onPointerUp );
|
|
66
54
|
} );
|