@automattic/charts 4.1.1 → 4.3.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 +39 -0
- package/TOKENS.md +1 -1
- package/dist/index.cjs +1335 -348
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +30 -1
- package/dist/index.d.cts +129 -8
- package/dist/index.d.ts +129 -8
- package/dist/index.js +1336 -351
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/charts/area-chart/area-chart.tsx +42 -10
- package/src/charts/area-chart/private/overlays.tsx +1 -2
- package/src/charts/area-chart/private/validate-data.ts +2 -3
- package/src/charts/area-chart/test/area-chart.test.tsx +72 -1
- package/src/charts/bar-chart/bar-chart.tsx +137 -70
- package/src/charts/bar-chart/index.ts +1 -1
- package/src/charts/bar-chart/private/band-highlight.tsx +87 -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 +3 -0
- package/src/charts/bar-chart/private/test/band-highlight.test.tsx +135 -0
- package/src/charts/bar-chart/private/test/band-tooltip.test.ts +26 -0
- package/src/charts/bar-chart/private/test/value-domain.test.ts +100 -0
- package/src/charts/bar-chart/private/truncated-tick-component.tsx +1 -1
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +13 -40
- package/src/charts/bar-chart/private/value-domain.ts +90 -0
- package/src/charts/bar-chart/test/bar-chart-pointer-events.test.tsx +11 -23
- package/src/charts/bar-chart/test/bar-chart.test.tsx +1058 -19
- package/src/charts/bar-chart/types.ts +39 -0
- 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/build-calendar-data.ts +5 -11
- 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 +6 -0
- 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 -0
- package/src/charts/heatmap-chart/private/keyboard-navigation.ts +201 -0
- package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +36 -0
- 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-month-calendar-heatmap-data.test.tsx +90 -0
- package/src/charts/heatmap-chart/test/zoned-month-calendar-data.test.ts +23 -0
- package/src/charts/heatmap-chart/types.ts +59 -3
- 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 +149 -36
- package/src/charts/line-chart/private/index.ts +1 -0
- package/src/charts/line-chart/private/line-chart-annotation.tsx +1 -1
- package/src/charts/line-chart/private/line-chart-glyph.tsx +17 -3
- package/src/charts/line-chart/private/nearest-pointer-events.tsx +155 -0
- package/src/charts/line-chart/test/line-chart.test.tsx +604 -9
- package/src/charts/line-chart/types.ts +4 -3
- package/src/charts/private/readings.ts +34 -0
- package/src/charts/private/test/readings.test.ts +29 -0
- package/src/charts/private/test/whole-number-ticks.test.tsx +57 -0
- package/src/charts/private/time-axis-options.ts +1 -1
- package/src/charts/private/whole-number-ticks.tsx +79 -0
- 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/hooks/test/use-chart-legend-items.test.tsx +124 -0
- package/src/components/legend/hooks/use-chart-legend-items.ts +60 -2
- package/src/components/legend/private/base-legend.tsx +37 -22
- package/src/components/legend/test/legend.test.tsx +84 -0
- package/src/components/legend/types.ts +2 -0
- package/src/components/legend/utils/index.ts +0 -1
- package/src/components/tooltip/accessible-tooltip.tsx +128 -41
- 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 +128 -0
- package/src/components/tooltip/test/bounded-tooltip.test.tsx +17 -0
- package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +7 -0
- package/src/components/tooltip/xy-chart-tooltip.tsx +9 -10
- package/src/hooks/test/use-chart-margin.test.tsx +35 -0
- package/src/hooks/test/use-element-size.test.tsx +13 -13
- package/src/hooks/use-chart-margin.tsx +7 -2
- package/src/hooks/use-xychart-theme.ts +1 -1
- package/src/index.ts +7 -1
- package/src/providers/chart-context/global-charts-provider.tsx +1 -1
- package/src/providers/chart-context/test/chart-context.test.tsx +4 -4
- package/src/providers/chart-context/themes.ts +1 -0
- package/src/styles/test/chart-scope.test.ts +1 -1
- package/src/types.ts +14 -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
- package/src/components/legend/utils/label-transform-factory.ts +0 -24
|
@@ -3,7 +3,7 @@ import { DataContext, TooltipContext } from '@visx/xychart';
|
|
|
3
3
|
import { useCallback, useContext, useState } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { isValidHexColor } from '../../utils';
|
|
6
|
-
import { BoundedTooltip } from './private/bounded-tooltip';
|
|
6
|
+
import { BoundedTooltip, TOOLTIP_Z_INDEX } from './private/bounded-tooltip';
|
|
7
7
|
import type {
|
|
8
8
|
CrosshairStyle,
|
|
9
9
|
RenderTooltipGlyphProps,
|
|
@@ -36,9 +36,6 @@ const crosshairPaintProps = ( props: CrosshairStyle = {} ) => {
|
|
|
36
36
|
};
|
|
37
37
|
const DEFAULT_GLYPH_RADIUS = 4;
|
|
38
38
|
const FALLBACK_COLOR = '#222';
|
|
39
|
-
// Above the chart's own overlays (the zoom reset button sits at 2). The chart
|
|
40
|
-
// wrapper isolates its stacking context, so this never competes with page chrome.
|
|
41
|
-
const DEFAULT_TOOLTIP_Z_INDEX = 3;
|
|
42
39
|
// Portal-era options, accepted for compatibility and dropped before the box
|
|
43
40
|
// renders: it sits in the chart wrapper and moves with it, and nothing
|
|
44
41
|
// measures it any more.
|
|
@@ -110,7 +107,8 @@ const XyChartTooltipContent = < Datum extends object >( {
|
|
|
110
107
|
horizontalCrosshairStyle,
|
|
111
108
|
detectBounds = true,
|
|
112
109
|
tooltipPlacement = 'auto',
|
|
113
|
-
|
|
110
|
+
tooltipAnchorTop,
|
|
111
|
+
zIndex = TOOLTIP_Z_INDEX,
|
|
114
112
|
style,
|
|
115
113
|
...rest
|
|
116
114
|
}: XyChartTooltipContentProps< Datum > ) => {
|
|
@@ -171,7 +169,9 @@ const XyChartTooltipContent = < Datum extends object >( {
|
|
|
171
169
|
const size = Number( glyphStyle?.radius ?? DEFAULT_GLYPH_RADIUS );
|
|
172
170
|
const labelColor = theme?.htmlLabel?.color ?? FALLBACK_COLOR;
|
|
173
171
|
// visx colours a lone nearest-datum glyph like the gridlines and series glyphs like labels.
|
|
174
|
-
const fallbackColor = showSeriesGlyphs
|
|
172
|
+
const fallbackColor = showSeriesGlyphs
|
|
173
|
+
? labelColor
|
|
174
|
+
: ( theme?.gridStyles?.stroke ?? labelColor );
|
|
175
175
|
let entries: Array< { key: string; datum: Datum; index: number } > = [];
|
|
176
176
|
if ( showSeriesGlyphs ) {
|
|
177
177
|
entries = Object.values( tooltipContext.tooltipData?.datumByKey ?? {} );
|
|
@@ -214,8 +214,7 @@ const XyChartTooltipContent = < Datum extends object >( {
|
|
|
214
214
|
const marginTop = margin?.top ?? 0;
|
|
215
215
|
const marginLeft = margin?.left ?? 0;
|
|
216
216
|
|
|
217
|
-
const TooltipComponent =
|
|
218
|
-
detectBounds || tooltipPlacement === 'below-axis' ? BoundedTooltip : Tooltip;
|
|
217
|
+
const TooltipComponent = detectBounds || tooltipPlacement !== 'auto' ? BoundedTooltip : Tooltip;
|
|
219
218
|
const boxStyle: CSSProperties = {
|
|
220
219
|
...defaultStyles,
|
|
221
220
|
zIndex,
|
|
@@ -265,12 +264,12 @@ const XyChartTooltipContent = < Datum extends object >( {
|
|
|
265
264
|
top={
|
|
266
265
|
tooltipPlacement === 'below-axis'
|
|
267
266
|
? marginTop + innerHeight + ( margin?.bottom ?? 0 )
|
|
268
|
-
: tooltipTop
|
|
267
|
+
: ( tooltipAnchorTop ?? tooltipTop )
|
|
269
268
|
}
|
|
270
269
|
style={ boxStyle }
|
|
271
270
|
applyPositionStyle
|
|
272
271
|
{ ...tooltipProps }
|
|
273
|
-
{ ...( tooltipPlacement
|
|
272
|
+
{ ...( tooltipPlacement !== 'auto' && { placement: tooltipPlacement } ) }
|
|
274
273
|
>
|
|
275
274
|
{ tooltipContent }
|
|
276
275
|
</TooltipComponent>,
|
|
@@ -353,6 +353,41 @@ describe( 'useChartMargin', () => {
|
|
|
353
353
|
|
|
354
354
|
expect( result.current.left ).toBe( 20 );
|
|
355
355
|
} );
|
|
356
|
+
|
|
357
|
+
it( 'measures ticks from readings that exist', () => {
|
|
358
|
+
const partialData = [
|
|
359
|
+
{
|
|
360
|
+
label: 'Series 1',
|
|
361
|
+
data: [
|
|
362
|
+
{ date: new Date( '2024-01-01' ), value: null },
|
|
363
|
+
{ date: new Date( '2024-01-02' ), value: 100 },
|
|
364
|
+
{ date: new Date( '2024-01-03' ), value: 200 },
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
renderHook( () => useChartMargin( 300, optionsBase, partialData, baseTheme ) );
|
|
370
|
+
|
|
371
|
+
const ticks = mockGetLongestTickWidth.mock.calls[ 0 ][ 0 ] as number[];
|
|
372
|
+
expect( Math.min( ...ticks ) ).toBe( 100 );
|
|
373
|
+
} );
|
|
374
|
+
|
|
375
|
+
it( 'measures a unit range when no bucket has a reading', () => {
|
|
376
|
+
const allNullData = [
|
|
377
|
+
{
|
|
378
|
+
label: 'Series 1',
|
|
379
|
+
data: [
|
|
380
|
+
{ date: new Date( '2024-01-01' ), value: null },
|
|
381
|
+
{ date: new Date( '2024-01-02' ), value: null },
|
|
382
|
+
],
|
|
383
|
+
},
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
renderHook( () => useChartMargin( 300, optionsBase, allNullData, baseTheme ) );
|
|
387
|
+
|
|
388
|
+
const ticks = mockGetLongestTickWidth.mock.calls[ 0 ][ 0 ] as number[];
|
|
389
|
+
expect( Math.max( ...ticks ) ).toBe( 1 );
|
|
390
|
+
} );
|
|
356
391
|
} );
|
|
357
392
|
|
|
358
393
|
describe( 'horizontal y ticks', () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { renderHook, waitFor } from '@testing-library/react';
|
|
1
|
+
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
2
2
|
import { useElementSize } from '../use-element-size';
|
|
3
3
|
|
|
4
4
|
// Mock ResizeObserver
|
|
@@ -66,7 +66,7 @@ describe( 'useElementSize', () => {
|
|
|
66
66
|
const [ refCallback ] = result.current;
|
|
67
67
|
|
|
68
68
|
// Attach the element
|
|
69
|
-
refCallback( mockElement as unknown as HTMLDivElement );
|
|
69
|
+
act( () => refCallback( mockElement as unknown as HTMLDivElement ) );
|
|
70
70
|
|
|
71
71
|
await waitFor( () => {
|
|
72
72
|
expect( result.current[ 1 ] ).toBe( 300 );
|
|
@@ -84,7 +84,7 @@ describe( 'useElementSize', () => {
|
|
|
84
84
|
const { result } = renderHook( () => useElementSize() );
|
|
85
85
|
const [ refCallback ] = result.current;
|
|
86
86
|
|
|
87
|
-
refCallback( mockElement as unknown as HTMLDivElement );
|
|
87
|
+
act( () => refCallback( mockElement as unknown as HTMLDivElement ) );
|
|
88
88
|
|
|
89
89
|
await waitFor( () => {
|
|
90
90
|
expect( result.current[ 1 ] ).toBe( 0 );
|
|
@@ -100,7 +100,7 @@ describe( 'useElementSize', () => {
|
|
|
100
100
|
const { result } = renderHook( () => useElementSize() );
|
|
101
101
|
const [ refCallback ] = result.current;
|
|
102
102
|
|
|
103
|
-
refCallback( mockElement as unknown as HTMLDivElement );
|
|
103
|
+
act( () => refCallback( mockElement as unknown as HTMLDivElement ) );
|
|
104
104
|
|
|
105
105
|
await waitFor( () => {
|
|
106
106
|
expect( result.current[ 1 ] ).toBe( 0 );
|
|
@@ -129,10 +129,10 @@ describe( 'useElementSize', () => {
|
|
|
129
129
|
const [ refCallback ] = result.current;
|
|
130
130
|
|
|
131
131
|
// Attach first element
|
|
132
|
-
refCallback( mockElement1 as unknown as HTMLDivElement );
|
|
132
|
+
act( () => refCallback( mockElement1 as unknown as HTMLDivElement ) );
|
|
133
133
|
|
|
134
134
|
// Attach second element
|
|
135
|
-
refCallback( mockElement2 as unknown as HTMLDivElement );
|
|
135
|
+
act( () => refCallback( mockElement2 as unknown as HTMLDivElement ) );
|
|
136
136
|
|
|
137
137
|
expect( disconnectSpy ).toHaveBeenCalled();
|
|
138
138
|
} );
|
|
@@ -155,10 +155,10 @@ describe( 'useElementSize', () => {
|
|
|
155
155
|
const [ refCallback ] = result.current;
|
|
156
156
|
|
|
157
157
|
// Attach element
|
|
158
|
-
refCallback( mockElement as unknown as HTMLDivElement );
|
|
158
|
+
act( () => refCallback( mockElement as unknown as HTMLDivElement ) );
|
|
159
159
|
|
|
160
160
|
// Remove element
|
|
161
|
-
refCallback( null as unknown as HTMLDivElement );
|
|
161
|
+
act( () => refCallback( null as unknown as HTMLDivElement ) );
|
|
162
162
|
|
|
163
163
|
expect( disconnectSpy ).toHaveBeenCalled();
|
|
164
164
|
} );
|
|
@@ -180,7 +180,7 @@ describe( 'useElementSize', () => {
|
|
|
180
180
|
const { result } = renderHook( () => useElementSize() );
|
|
181
181
|
const [ refCallback ] = result.current;
|
|
182
182
|
|
|
183
|
-
refCallback( mockElement as unknown as HTMLDivElement );
|
|
183
|
+
act( () => refCallback( mockElement as unknown as HTMLDivElement ) );
|
|
184
184
|
|
|
185
185
|
expect( globalThis.ResizeObserver ).toHaveBeenCalled();
|
|
186
186
|
expect( observeSpy ).toHaveBeenCalledWith( mockElement );
|
|
@@ -207,7 +207,7 @@ describe( 'useElementSize', () => {
|
|
|
207
207
|
const { result } = renderHook( () => useElementSize() );
|
|
208
208
|
const [ refCallback ] = result.current;
|
|
209
209
|
|
|
210
|
-
refCallback( mockSpanElement as unknown as HTMLDivElement );
|
|
210
|
+
act( () => refCallback( mockSpanElement as unknown as HTMLDivElement ) );
|
|
211
211
|
|
|
212
212
|
await waitFor( () => {
|
|
213
213
|
expect( result.current[ 1 ] ).toBe( 75 );
|
|
@@ -236,7 +236,7 @@ describe( 'useElementSize', () => {
|
|
|
236
236
|
const { result } = renderHook( () => useElementSize() );
|
|
237
237
|
const [ refCallback ] = result.current;
|
|
238
238
|
|
|
239
|
-
refCallback( mockElement as unknown as HTMLDivElement );
|
|
239
|
+
act( () => refCallback( mockElement as unknown as HTMLDivElement ) );
|
|
240
240
|
|
|
241
241
|
await waitFor( () => {
|
|
242
242
|
expect( result.current[ 1 ] ).toBe( 100 );
|
|
@@ -244,7 +244,7 @@ describe( 'useElementSize', () => {
|
|
|
244
244
|
} );
|
|
245
245
|
|
|
246
246
|
// Simulate resize
|
|
247
|
-
resizeCallback();
|
|
247
|
+
act( () => resizeCallback() );
|
|
248
248
|
|
|
249
249
|
await waitFor( () => {
|
|
250
250
|
expect( result.current[ 1 ] ).toBe( 200 );
|
|
@@ -269,7 +269,7 @@ describe( 'useElementSize', () => {
|
|
|
269
269
|
const { result, unmount } = renderHook( () => useElementSize() );
|
|
270
270
|
const [ refCallback ] = result.current;
|
|
271
271
|
|
|
272
|
-
refCallback( mockElement as unknown as HTMLDivElement );
|
|
272
|
+
act( () => refCallback( mockElement as unknown as HTMLDivElement ) );
|
|
273
273
|
unmount();
|
|
274
274
|
|
|
275
275
|
// The observer should be cleaned up when the hook unmounts
|
|
@@ -111,8 +111,13 @@ export const useChartMargin = (
|
|
|
111
111
|
return options.axis.y.tickValues;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
const
|
|
115
|
-
|
|
114
|
+
const values = allDataPoints
|
|
115
|
+
.map( d => d.value )
|
|
116
|
+
.filter(
|
|
117
|
+
( value ): value is number => typeof value === 'number' && Number.isFinite( value )
|
|
118
|
+
);
|
|
119
|
+
const minY = values.length ? Math.min( ...values ) : 0;
|
|
120
|
+
const maxY = values.length ? Math.max( ...values ) : 1;
|
|
116
121
|
const yScale = createScale( {
|
|
117
122
|
...options.yScale,
|
|
118
123
|
// A pinned domain is what the axis actually renders, so measure those
|
|
@@ -23,7 +23,7 @@ export const useXYChartTheme = ( data: SeriesData[] ) => {
|
|
|
23
23
|
// One resolver for all three, so they share a single getComputedStyle call.
|
|
24
24
|
const resolve = createCssVariableResolver( scopeElement );
|
|
25
25
|
const resolveColor = ( value?: string ): string | undefined =>
|
|
26
|
-
value ? resolve( value ) ?? value : value;
|
|
26
|
+
value ? ( resolve( value ) ?? value ) : value;
|
|
27
27
|
|
|
28
28
|
const seriesColors: string[] = JSON.parse( seriesColorKey );
|
|
29
29
|
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,9 @@ export {
|
|
|
10
10
|
HeatmapChart,
|
|
11
11
|
HeatmapChartUnresponsive,
|
|
12
12
|
buildCalendarHeatmapData,
|
|
13
|
+
buildMonthCalendarHeatmapData,
|
|
13
14
|
useCalendarHeatmapData,
|
|
15
|
+
useMonthCalendarHeatmapData,
|
|
14
16
|
} from './charts/heatmap-chart';
|
|
15
17
|
export { LeaderboardChart, LeaderboardChartUnresponsive } from './charts/leaderboard-chart';
|
|
16
18
|
export { LineChart, LineChartUnresponsive } from './charts/line-chart';
|
|
@@ -95,7 +97,7 @@ export type { LegendProps, BaseLegendProps, ChartLegendOptions } from './compone
|
|
|
95
97
|
|
|
96
98
|
// Previously available via '@automattic/charts/bar-chart', '@automattic/charts/line-chart', etc.
|
|
97
99
|
export type { AreaChartProps } from './charts/area-chart';
|
|
98
|
-
export type { BarChartProps } from './charts/bar-chart';
|
|
100
|
+
export type { BarChartProps, BandHighlightSelection } from './charts/bar-chart';
|
|
99
101
|
export type {
|
|
100
102
|
BarListChartProps,
|
|
101
103
|
RenderLabelProps,
|
|
@@ -112,10 +114,14 @@ export type {
|
|
|
112
114
|
export type {
|
|
113
115
|
HeatmapChartProps,
|
|
114
116
|
HeatmapColumn,
|
|
117
|
+
HeatmapColumnGroup,
|
|
115
118
|
HeatmapCell,
|
|
116
119
|
HeatmapTooltipData,
|
|
117
120
|
CalendarHeatmapOptions,
|
|
118
121
|
CalendarHeatmapResult,
|
|
122
|
+
MonthCalendarHeatmapOptions,
|
|
123
|
+
MonthCalendarHeatmapRange,
|
|
124
|
+
MonthCalendarHeatmapResult,
|
|
119
125
|
} from './charts/heatmap-chart';
|
|
120
126
|
export type { LeaderboardChartProps } from './charts/leaderboard-chart';
|
|
121
127
|
export type {
|
|
@@ -219,7 +219,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
|
|
|
219
219
|
|
|
220
220
|
const getElementStyles = useCallback< GlobalChartsContextValue[ 'getElementStyles' ] >(
|
|
221
221
|
( { data, index, overrideColor, legendShape } ) => {
|
|
222
|
-
const isSeriesData = data && typeof data === 'object' && 'data' in data
|
|
222
|
+
const isSeriesData = data && typeof data === 'object' && 'data' in data;
|
|
223
223
|
// DataPointPercentage has a numeric 'value' directly, unlike SeriesData which has 'data' array
|
|
224
224
|
const isPointPercentageData =
|
|
225
225
|
data &&
|
|
@@ -1318,11 +1318,11 @@ describe( 'ChartContext', () => {
|
|
|
1318
1318
|
expect( styles ).toHaveProperty( 'glyph' );
|
|
1319
1319
|
expect( styles ).toHaveProperty( 'shapeStyles' );
|
|
1320
1320
|
|
|
1321
|
-
//
|
|
1321
|
+
// A series without an `options` key is still a series, so theme styles apply.
|
|
1322
1322
|
expect( typeof styles.color ).toBe( 'string' );
|
|
1323
|
-
expect(
|
|
1324
|
-
expect(
|
|
1325
|
-
expect(
|
|
1323
|
+
expect( styles.lineStyles ).toEqual( { strokeWidth: 2 } );
|
|
1324
|
+
expect( styles.glyph ).toBe( mockGlyph );
|
|
1325
|
+
expect( styles.shapeStyles ).toEqual( { fill: '#SHAPE1' } );
|
|
1326
1326
|
} );
|
|
1327
1327
|
|
|
1328
1328
|
it( 'returns complete ElementStyles object for DataPointPercentage', () => {
|
|
@@ -101,7 +101,7 @@ function parseTokensDoc(): Map< string, Entry > {
|
|
|
101
101
|
const token = /(--wp[\w-]+)/.exec( mapping );
|
|
102
102
|
|
|
103
103
|
entries.set( role, {
|
|
104
|
-
reads: unmapped ? null : derives?.[ 1 ] ?? token?.[ 1 ] ?? null,
|
|
104
|
+
reads: unmapped ? null : ( derives?.[ 1 ] ?? token?.[ 1 ] ?? null ),
|
|
105
105
|
fallback: fallback === '—' ? null : normalize( fallback ),
|
|
106
106
|
} );
|
|
107
107
|
}
|
package/src/types.ts
CHANGED
|
@@ -53,12 +53,7 @@ export type LegendShape< Data, Output > =
|
|
|
53
53
|
| ComponentClass< LegendShapeRenderProps< Data, Output > >;
|
|
54
54
|
|
|
55
55
|
export type GoogleDataTableColumnType =
|
|
56
|
-
| '
|
|
57
|
-
| 'number'
|
|
58
|
-
| 'boolean'
|
|
59
|
-
| 'date'
|
|
60
|
-
| 'datetime'
|
|
61
|
-
| 'timeofday';
|
|
56
|
+
'string' | 'number' | 'boolean' | 'date' | 'datetime' | 'timeofday';
|
|
62
57
|
|
|
63
58
|
export enum GoogleDataTableColumnRoleType {
|
|
64
59
|
annotation = 'annotation',
|
|
@@ -98,13 +93,7 @@ export type GoogleDataTableCell =
|
|
|
98
93
|
export type GoogleDataTableRow = GoogleDataTableCell[];
|
|
99
94
|
|
|
100
95
|
export type ChartType =
|
|
101
|
-
| '
|
|
102
|
-
| 'bar'
|
|
103
|
-
| 'conversion-funnel'
|
|
104
|
-
| 'leaderboard'
|
|
105
|
-
| 'line'
|
|
106
|
-
| 'pie'
|
|
107
|
-
| 'pie-semi-circle';
|
|
96
|
+
'area' | 'bar' | 'conversion-funnel' | 'leaderboard' | 'line' | 'pie' | 'pie-semi-circle';
|
|
108
97
|
|
|
109
98
|
export type OrientationType = ValueOf< typeof Orientation >;
|
|
110
99
|
|
|
@@ -145,6 +134,8 @@ export type ThemeAnnotationStyles = {
|
|
|
145
134
|
export type DataPoint = {
|
|
146
135
|
label: string;
|
|
147
136
|
value: number;
|
|
137
|
+
/** Per-point fill override for BarChart; other built-in charts ignore it. */
|
|
138
|
+
color?: string;
|
|
148
139
|
};
|
|
149
140
|
|
|
150
141
|
/**
|
|
@@ -196,6 +187,8 @@ export type DataPointDate = {
|
|
|
196
187
|
dateString?: string;
|
|
197
188
|
value: number | null;
|
|
198
189
|
label?: string;
|
|
190
|
+
/** Per-point fill override for BarChart; other built-in charts ignore it. */
|
|
191
|
+
color?: string;
|
|
199
192
|
};
|
|
200
193
|
|
|
201
194
|
export type LeaderboardEntry = {
|
|
@@ -424,6 +417,8 @@ export type ChartTheme = {
|
|
|
424
417
|
compactCellGap?: number;
|
|
425
418
|
/** Fixed square cell size in px for compact mode */
|
|
426
419
|
compactCellSize?: number;
|
|
420
|
+
/** Gap in px between adjacent column groups; in compact mode the minimum, as the gaps share leftover width */
|
|
421
|
+
groupGap?: number;
|
|
427
422
|
};
|
|
428
423
|
};
|
|
429
424
|
|
|
@@ -657,6 +652,12 @@ export type SeriesChartLegendConfig = ChartLegendConfig< SeriesData[] > & {
|
|
|
657
652
|
* uncollapsed one toggles only its own.
|
|
658
653
|
*/
|
|
659
654
|
collapseGroups?: boolean;
|
|
655
|
+
/**
|
|
656
|
+
* Append a static, non-interactive item explaining the comparison overlay whenever a series
|
|
657
|
+
* has `options.type === 'comparison'`. Skipped when that series already has its own item.
|
|
658
|
+
* Pass a string to replace the default label.
|
|
659
|
+
*/
|
|
660
|
+
comparisonItem?: boolean | string;
|
|
660
661
|
};
|
|
661
662
|
|
|
662
663
|
/**
|
|
@@ -47,13 +47,13 @@ export const formatMetricValue = (
|
|
|
47
47
|
maximumFractionDigits: decimals ?? 2,
|
|
48
48
|
signDisplay,
|
|
49
49
|
},
|
|
50
|
-
|
|
50
|
+
} )
|
|
51
51
|
: formatNumber( numericValue, {
|
|
52
52
|
decimals: decimals ?? 2,
|
|
53
53
|
numberFormatOptions: {
|
|
54
54
|
signDisplay,
|
|
55
55
|
},
|
|
56
|
-
|
|
56
|
+
} );
|
|
57
57
|
return `$${ formatted }`;
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -80,13 +80,13 @@ export const formatMetricValue = (
|
|
|
80
80
|
maximumFractionDigits: decimals ?? 0,
|
|
81
81
|
signDisplay,
|
|
82
82
|
},
|
|
83
|
-
|
|
83
|
+
} )
|
|
84
84
|
: formatNumber( numericValue, {
|
|
85
85
|
decimals: decimals ?? 0,
|
|
86
86
|
numberFormatOptions: {
|
|
87
87
|
signDisplay,
|
|
88
88
|
},
|
|
89
|
-
|
|
89
|
+
} );
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
};
|
|
@@ -24,7 +24,7 @@ export const getEdgeTickWidths = < T extends AnyD3Scale >(
|
|
|
24
24
|
|
|
25
25
|
const lastIndex = ticks.length - 1;
|
|
26
26
|
const label = ( tick: ScaleInput< T >, index: number ) =>
|
|
27
|
-
String( formatTick ? formatTick( tick, index, [] ) ?? '' : tick );
|
|
27
|
+
String( formatTick ? ( formatTick( tick, index, [] ) ?? '' ) : tick );
|
|
28
28
|
|
|
29
29
|
return {
|
|
30
30
|
first: getStringWidth( label( ticks[ 0 ], 0 ), labelStyle ) ?? 0,
|
package/src/utils/get-styles.ts
CHANGED
|
@@ -103,7 +103,7 @@ export function getItemShapeStyles(
|
|
|
103
103
|
const hasExplicitStyles = Object.values( explicitStyles ).some(
|
|
104
104
|
value => value !== undefined && value !== null && value !== ''
|
|
105
105
|
);
|
|
106
|
-
const baseShapeStyles = hasExplicitStyles ? explicitStyles : themeShapeStyles ?? {};
|
|
106
|
+
const baseShapeStyles = hasExplicitStyles ? explicitStyles : ( themeShapeStyles ?? {} );
|
|
107
107
|
|
|
108
108
|
// Layer the comparison bar opacity on top so the swatch matches the translucent bar
|
|
109
109
|
// without discarding the base (custom or theme) shape styles.
|
package/src/visx/types.ts
CHANGED
|
@@ -12,15 +12,10 @@ export interface RenderTooltipGlyphProps< Datum extends object > extends GlyphPr
|
|
|
12
12
|
isNearestDatum: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export type TooltipPlacement = 'auto' | 'below-axis';
|
|
15
|
+
export type TooltipPlacement = 'auto' | 'below-axis' | 'beside';
|
|
16
16
|
|
|
17
17
|
type CrosshairPaintProperty =
|
|
18
|
-
| '
|
|
19
|
-
| 'strokeWidth'
|
|
20
|
-
| 'strokeOpacity'
|
|
21
|
-
| 'strokeDasharray'
|
|
22
|
-
| 'strokeLinecap'
|
|
23
|
-
| 'opacity';
|
|
18
|
+
'stroke' | 'strokeWidth' | 'strokeOpacity' | 'strokeDasharray' | 'strokeLinecap' | 'opacity';
|
|
24
19
|
|
|
25
20
|
export type CrosshairStyle = Pick< SVGProps< SVGLineElement >, CrosshairPaintProperty > & {
|
|
26
21
|
className?: string;
|
|
@@ -31,11 +26,13 @@ export type XyChartTooltipProps< Datum extends object > = {
|
|
|
31
26
|
renderTooltip: ( params: RenderTooltipParams< Datum > ) => ReactNode;
|
|
32
27
|
renderGlyph?: ( params: RenderTooltipGlyphProps< Datum > ) => ReactNode;
|
|
33
28
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
29
|
+
* Use below-axis placement for centered axis anchoring, or beside placement to avoid vertical flipping.
|
|
30
|
+
* Below-axis ignores vertical bounds; beside placement clamps to them.
|
|
36
31
|
* @default 'auto'
|
|
37
32
|
*/
|
|
38
33
|
tooltipPlacement?: TooltipPlacement;
|
|
34
|
+
/** Override the tooltip top anchor in SVG coordinates, including negative offsets. */
|
|
35
|
+
tooltipAnchorTop?: number;
|
|
39
36
|
/** Merge overrides with the default box styles; use `unstyled` to strip the box styling. */
|
|
40
37
|
style?: VisxTooltipProps[ 'style' ];
|
|
41
38
|
snapTooltipToDatumX?: boolean;
|
|
@@ -52,7 +49,7 @@ export type XyChartTooltipProps< Datum extends object > = {
|
|
|
52
49
|
* that clips its overflow, or the viewport when there is none. The box may
|
|
53
50
|
* leave the chart wrapper. (It used to keep a body-level portal inside the
|
|
54
51
|
* viewport.)
|
|
55
|
-
*
|
|
52
|
+
* Non-auto placements always apply their own bounds handling.
|
|
56
53
|
* @default true
|
|
57
54
|
*/
|
|
58
55
|
detectBounds?: boolean;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { ItemTransformer, LabelFormatter } from '@visx/legend';
|
|
2
|
-
import type { AnyD3Scale, ScaleInput } from '@visx/scale';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Returns a function which takes a Datum and index as input, and returns a formatted label object.
|
|
6
|
-
* @param {object} root0 - The object to return the value of.
|
|
7
|
-
* @param {AnyD3Scale} root0.scale - The scale to use.
|
|
8
|
-
* @param {LabelFormatter<ScaleInput<Scale>>} root0.labelFormat - The label format to use.
|
|
9
|
-
* @return {ItemTransformer<ScaleInput<Scale>, ReturnType<Scale>>} The label transform factory.
|
|
10
|
-
*/
|
|
11
|
-
export function labelTransformFactory< Scale extends AnyD3Scale >( {
|
|
12
|
-
scale,
|
|
13
|
-
labelFormat,
|
|
14
|
-
}: {
|
|
15
|
-
scale: Scale;
|
|
16
|
-
labelFormat: LabelFormatter< ScaleInput< Scale > >;
|
|
17
|
-
} ): ItemTransformer< ScaleInput< Scale >, ReturnType< Scale > > {
|
|
18
|
-
return ( d, i ) => ( {
|
|
19
|
-
datum: d,
|
|
20
|
-
index: i,
|
|
21
|
-
text: `${ labelFormat( d, i ) }`,
|
|
22
|
-
value: scale( d ),
|
|
23
|
-
} );
|
|
24
|
-
}
|