@automattic/charts 4.11.1 → 4.12.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 +13 -0
- package/dist/index.cjs +856 -236
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +19 -13
- package/dist/index.d.cts +43 -4
- package/dist/index.d.ts +43 -4
- package/dist/index.js +855 -237
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/charts/bar-chart/bar-chart.tsx +134 -138
- package/src/charts/bar-chart/index.ts +3 -0
- package/src/charts/bar-chart/private/band-tooltip.tsx +41 -15
- package/src/charts/bar-chart/private/bar-layer.tsx +132 -0
- package/src/charts/bar-chart/private/comparison-bars-geometry.ts +22 -34
- package/src/charts/bar-chart/private/comparison-bars.tsx +9 -18
- package/src/charts/bar-chart/private/comparison-constants.ts +4 -10
- package/src/charts/bar-chart/private/grouped-bars.ts +68 -0
- package/src/charts/bar-chart/private/index.ts +7 -1
- package/src/charts/bar-chart/private/overlap-bars.ts +183 -0
- package/src/charts/bar-chart/private/selected-bar-ring.tsx +84 -0
- package/src/charts/bar-chart/private/slot-tooltip-members.tsx +29 -0
- package/src/charts/bar-chart/private/slotted-bar-series.tsx +84 -0
- package/src/charts/bar-chart/private/test/comparison-bars-geometry.test.ts +4 -11
- package/src/charts/bar-chart/private/test/comparison-bars.test.tsx +2 -3
- package/src/charts/bar-chart/private/test/grouped-bars.test.ts +87 -0
- package/src/charts/bar-chart/private/test/overlap-bars.test.ts +159 -0
- package/src/charts/bar-chart/private/use-bar-stops.ts +89 -0
- package/src/charts/bar-chart/private/use-overlap-slots.ts +194 -0
- package/src/charts/bar-chart/private/value-domain.ts +0 -10
- package/src/charts/bar-chart/test/bar-chart-overlap.test.tsx +347 -0
- package/src/charts/bar-chart/test/bar-chart.test.tsx +66 -19
- package/src/charts/bar-chart/test/use-bar-legend-items.test.tsx +81 -0
- package/src/charts/bar-chart/types.ts +3 -0
- package/src/charts/bar-chart/use-bar-colors.ts +46 -0
- package/src/charts/bar-chart/use-bar-legend-items.ts +87 -0
- package/src/charts/line-chart/line-chart.tsx +22 -17
- package/src/charts/line-chart/test/line-chart.test.tsx +165 -1
- package/src/charts/pie-chart/pie-chart.tsx +7 -2
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +7 -2
- package/src/charts/private/pie-keyboard-navigation/index.ts +0 -1
- package/src/charts/private/{pie-keyboard-navigation/pie-keyboard-navigation.module.scss → selected-ring/selected-ring.module.scss} +11 -6
- package/src/charts/private/selected-ring/selected-ring.tsx +73 -0
- package/src/charts/private/{pie-keyboard-navigation/test/selected-segment-ring.test.ts → selected-ring/test/selected-ring.test.ts} +1 -1
- package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +20 -0
- package/src/components/legend/hooks/use-chart-legend-items.ts +27 -3
- package/src/components/tooltip/xy-chart-tooltip.tsx +3 -2
- package/src/index.ts +8 -2
- package/src/providers/chart-context/global-charts-provider.tsx +2 -0
- package/src/providers/chart-context/test/chart-context.test.tsx +0 -13
- package/src/providers/chart-context/themes.ts +0 -1
- package/src/providers/chart-context/types.ts +2 -0
- package/src/test-utils/selected-bar-ring.ts +14 -0
- package/src/types.ts +10 -2
- package/src/charts/private/pie-keyboard-navigation/selected-segment-ring.tsx +0 -47
|
@@ -31,10 +31,10 @@ import { pickLabelTextColor, resolveLabelRoles } from '../private/label-text-col
|
|
|
31
31
|
import {
|
|
32
32
|
orderArcsForNavigation,
|
|
33
33
|
PieSelectionOutput,
|
|
34
|
-
SelectedSegmentRing,
|
|
35
34
|
usePieKeyboardNavigation,
|
|
36
35
|
} from '../private/pie-keyboard-navigation';
|
|
37
36
|
import { RadialWipeAnimation } from '../private/radial-wipe-animation/';
|
|
37
|
+
import { SelectedRing } from '../private/selected-ring/selected-ring';
|
|
38
38
|
import { getAllHiddenMessage, SvgEmptyState } from '../private/svg-empty-state';
|
|
39
39
|
import { withResponsive, ResponsiveConfig } from '../private/with-responsive';
|
|
40
40
|
import styles from './pie-chart.module.scss';
|
|
@@ -503,7 +503,12 @@ const PieChartInternal = ( {
|
|
|
503
503
|
{ /* Unmasked so the entry wipe cannot hide the focus indicator. */ }
|
|
504
504
|
{ selectedArc && (
|
|
505
505
|
<Group top={ centerY } left={ centerX }>
|
|
506
|
-
<
|
|
506
|
+
<SelectedRing
|
|
507
|
+
chartId={ chartId }
|
|
508
|
+
d={ path( selectedArc ) || '' }
|
|
509
|
+
side="inside"
|
|
510
|
+
testId="pie-selected-ring"
|
|
511
|
+
/>
|
|
507
512
|
</Group>
|
|
508
513
|
) }
|
|
509
514
|
{ ! allSegmentsHidden && (
|
|
@@ -26,10 +26,10 @@ import { ChartLayout } from '../private/chart-layout';
|
|
|
26
26
|
import {
|
|
27
27
|
orderArcsForNavigation,
|
|
28
28
|
PieSelectionOutput,
|
|
29
|
-
SelectedSegmentRing,
|
|
30
29
|
usePieKeyboardNavigation,
|
|
31
30
|
} from '../private/pie-keyboard-navigation';
|
|
32
31
|
import { RadialWipeAnimation } from '../private/radial-wipe-animation';
|
|
32
|
+
import { SelectedRing } from '../private/selected-ring/selected-ring';
|
|
33
33
|
import { getAllHiddenMessage, SvgEmptyState } from '../private/svg-empty-state';
|
|
34
34
|
import { withResponsive } from '../private/with-responsive';
|
|
35
35
|
import styles from './pie-semi-circle-chart.module.scss';
|
|
@@ -429,7 +429,12 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
429
429
|
{ /* Unmasked so the entry wipe cannot hide the focus indicator. */ }
|
|
430
430
|
{ selectedArc && (
|
|
431
431
|
<Group top={ height } left={ width / 2 }>
|
|
432
|
-
<
|
|
432
|
+
<SelectedRing
|
|
433
|
+
chartId={ chartId }
|
|
434
|
+
d={ path( selectedArc ) || '' }
|
|
435
|
+
side="inside"
|
|
436
|
+
testId="pie-selected-ring"
|
|
437
|
+
/>
|
|
433
438
|
</Group>
|
|
434
439
|
) }
|
|
435
440
|
{ ! allSegmentsHidden && (
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
// The clip
|
|
2
|
-
|
|
3
|
-
.selected-
|
|
4
|
-
.selected-ring__focus {
|
|
1
|
+
// The clip removes one half of each stroke, so widths are doubled.
|
|
2
|
+
.selected-ring__far,
|
|
3
|
+
.selected-ring__edge {
|
|
5
4
|
fill: none;
|
|
6
5
|
}
|
|
7
6
|
|
|
7
|
+
.selected-ring__far {
|
|
8
|
+
stroke-width: calc(4 * var(--wpds-border-width-focus));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.selected-ring__edge {
|
|
12
|
+
stroke-width: calc(2 * var(--wpds-border-width-focus));
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
.selected-ring__contrast {
|
|
9
16
|
stroke: var(--a8c-charts-color-background, #fff);
|
|
10
|
-
stroke-width: calc(4 * var(--wpds-border-width-focus));
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
.selected-ring__focus {
|
|
14
20
|
stroke: var(--wpds-color-stroke-focus);
|
|
15
|
-
stroke-width: calc(2 * var(--wpds-border-width-focus));
|
|
16
21
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import styles from './selected-ring.module.scss';
|
|
3
|
+
|
|
4
|
+
type Tone = 'contrast' | 'focus';
|
|
5
|
+
|
|
6
|
+
/** Which side of the shape's edge the ring is drawn on. */
|
|
7
|
+
export type SelectedRingSide = 'inside' | 'outside';
|
|
8
|
+
|
|
9
|
+
interface SelectedRingProps {
|
|
10
|
+
/** The chart's id, which keeps the clip path unique on a page with several charts. */
|
|
11
|
+
chartId: string;
|
|
12
|
+
/** The selected shape's path. */
|
|
13
|
+
d: string;
|
|
14
|
+
/** `inside` keeps the ring clear of the SVG edge; `outside` leaves the shape's fill uncovered. */
|
|
15
|
+
side: SelectedRingSide;
|
|
16
|
+
testId?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// The tone at the edge is drawn last, over the wider tone beyond it.
|
|
20
|
+
const TONES: Record< SelectedRingSide, [ far: Tone, edge: Tone ] > = {
|
|
21
|
+
inside: [ 'contrast', 'focus' ],
|
|
22
|
+
outside: [ 'focus', 'contrast' ],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const EVERYWHERE = 'M-1e6 -1e6H1e6V1e6H-1e6Z';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Encodes `value` for an SVG id, losslessly so distinct chart ids never share a clip path.
|
|
29
|
+
*
|
|
30
|
+
* @param value - Any string.
|
|
31
|
+
* @return `value` with each character outside `[A-Za-z0-9-]` written as `_<hex code point>_`.
|
|
32
|
+
*/
|
|
33
|
+
export const toSvgId = ( value: string ) =>
|
|
34
|
+
Array.from( value, character =>
|
|
35
|
+
/[A-Za-z0-9-]/.test( character )
|
|
36
|
+
? character
|
|
37
|
+
: `_${ character.codePointAt( 0 )?.toString( 16 ) }_`
|
|
38
|
+
).join( '' );
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Marks a selected shape with a focus-colored ring and a contrast ring, so it reads against any fill.
|
|
42
|
+
*
|
|
43
|
+
* @param {SelectedRingProps} props - Component props
|
|
44
|
+
* @return {JSX.Element} The ring
|
|
45
|
+
*/
|
|
46
|
+
export const SelectedRing = ( { chartId, d, side, testId }: SelectedRingProps ) => {
|
|
47
|
+
const clipId = `selected-ring-clip-${ toSvgId( chartId ) }`;
|
|
48
|
+
const clipPath = `url(#${ clipId })`;
|
|
49
|
+
const [ far, edge ] = TONES[ side ];
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<g pointerEvents="none" aria-hidden="true">
|
|
53
|
+
<clipPath id={ clipId }>
|
|
54
|
+
{ side === 'inside' ? (
|
|
55
|
+
<path d={ d } />
|
|
56
|
+
) : (
|
|
57
|
+
<path d={ `${ EVERYWHERE } ${ d }` } clipRule="evenodd" />
|
|
58
|
+
) }
|
|
59
|
+
</clipPath>
|
|
60
|
+
<path
|
|
61
|
+
d={ d }
|
|
62
|
+
clipPath={ clipPath }
|
|
63
|
+
className={ clsx( styles[ 'selected-ring__far' ], styles[ `selected-ring__${ far }` ] ) }
|
|
64
|
+
/>
|
|
65
|
+
<path
|
|
66
|
+
d={ d }
|
|
67
|
+
clipPath={ clipPath }
|
|
68
|
+
className={ clsx( styles[ 'selected-ring__edge' ], styles[ `selected-ring__${ edge }` ] ) }
|
|
69
|
+
data-testid={ testId }
|
|
70
|
+
/>
|
|
71
|
+
</g>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
@@ -421,6 +421,26 @@ describe( 'useChartLegendItems', () => {
|
|
|
421
421
|
expect( result.current[ 2 ].color ).toBe( result.current[ 0 ].color );
|
|
422
422
|
} );
|
|
423
423
|
|
|
424
|
+
test( 'colors each swatch, comparison items included, from seriesColors by series index', () => {
|
|
425
|
+
const seriesColors = [ '#111111', '#222222', '#333333', '#444444' ];
|
|
426
|
+
const { result } = renderHook(
|
|
427
|
+
() =>
|
|
428
|
+
useChartLegendItems( twoMetricData, {
|
|
429
|
+
collapseGroups: true,
|
|
430
|
+
comparisonSeriesItems: perMetricLabel,
|
|
431
|
+
seriesColors,
|
|
432
|
+
} ),
|
|
433
|
+
{ wrapper }
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
expect( result.current.map( item => item.color ) ).toEqual( [
|
|
437
|
+
'#111111',
|
|
438
|
+
'#333333',
|
|
439
|
+
'#222222',
|
|
440
|
+
'#444444',
|
|
441
|
+
] );
|
|
442
|
+
} );
|
|
443
|
+
|
|
424
444
|
test( 'adds nothing when no series is a comparison', () => {
|
|
425
445
|
const { result } = renderHook(
|
|
426
446
|
() => useChartLegendItems( [ comparisonData[ 0 ] ], { comparisonItem: true } ),
|
|
@@ -41,6 +41,25 @@ export interface ChartLegendOptions {
|
|
|
41
41
|
comparisonItem?: SeriesChartLegendConfig[ 'comparisonItem' ];
|
|
42
42
|
/** One static item per comparison series instead; see `SeriesChartLegendConfig`. */
|
|
43
43
|
comparisonSeriesItems?: SeriesChartLegendConfig[ 'comparisonSeriesItems' ];
|
|
44
|
+
/** Each series' swatch color, by index, in place of its series color. */
|
|
45
|
+
seriesColors?: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Wraps `getElementStyles` so a swatch takes the color given for its series.
|
|
50
|
+
* @param getElementStyles - Function to get element styles
|
|
51
|
+
* @param seriesColors - Each series' swatch color, by index
|
|
52
|
+
* @return Element styles with the given color
|
|
53
|
+
*/
|
|
54
|
+
function withSeriesColors(
|
|
55
|
+
getElementStyles: ( params: GetElementStylesParams ) => ElementStyles,
|
|
56
|
+
seriesColors: string[]
|
|
57
|
+
): ( params: GetElementStylesParams ) => ElementStyles {
|
|
58
|
+
return params => {
|
|
59
|
+
const styles = getElementStyles( params );
|
|
60
|
+
|
|
61
|
+
return { ...styles, color: seriesColors[ params.index ] ?? styles.color };
|
|
62
|
+
};
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
/**
|
|
@@ -386,9 +405,10 @@ export function useChartLegendItems<
|
|
|
386
405
|
collapseGroups = false,
|
|
387
406
|
comparisonItem = false,
|
|
388
407
|
comparisonSeriesItems,
|
|
408
|
+
seriesColors,
|
|
389
409
|
renderGlyph,
|
|
390
410
|
} = options;
|
|
391
|
-
const { getElementStyles } = useGlobalChartsContext();
|
|
411
|
+
const { getElementStyles: getSeriesStyles } = useGlobalChartsContext();
|
|
392
412
|
|
|
393
413
|
return useMemo( () => {
|
|
394
414
|
if ( ! data || ! Array.isArray( data ) || data.length === 0 ) {
|
|
@@ -398,6 +418,9 @@ export function useChartLegendItems<
|
|
|
398
418
|
// Handle SeriesData (multiple series with data points)
|
|
399
419
|
if ( 'data' in data[ 0 ] ) {
|
|
400
420
|
const seriesData = data as SeriesData[];
|
|
421
|
+
const getElementStyles = seriesColors
|
|
422
|
+
? withSeriesColors( getSeriesStyles, seriesColors )
|
|
423
|
+
: getSeriesStyles;
|
|
401
424
|
const items = processSeriesData(
|
|
402
425
|
seriesData,
|
|
403
426
|
getElementStyles,
|
|
@@ -440,7 +463,7 @@ export function useChartLegendItems<
|
|
|
440
463
|
// Handle DataPointDate or DataPointPercentageCalculated (single data points)
|
|
441
464
|
return processPointData(
|
|
442
465
|
data as ( DataPointDate | DataPointPercentageCalculated )[],
|
|
443
|
-
|
|
466
|
+
getSeriesStyles,
|
|
444
467
|
showValues,
|
|
445
468
|
legendValueDisplay,
|
|
446
469
|
withGlyph,
|
|
@@ -450,7 +473,7 @@ export function useChartLegendItems<
|
|
|
450
473
|
);
|
|
451
474
|
}, [
|
|
452
475
|
data,
|
|
453
|
-
|
|
476
|
+
getSeriesStyles,
|
|
454
477
|
showValues,
|
|
455
478
|
legendValueDisplay,
|
|
456
479
|
withGlyph,
|
|
@@ -458,6 +481,7 @@ export function useChartLegendItems<
|
|
|
458
481
|
collapseGroups,
|
|
459
482
|
comparisonItem,
|
|
460
483
|
comparisonSeriesItems,
|
|
484
|
+
seriesColors,
|
|
461
485
|
renderGlyph,
|
|
462
486
|
legendShape,
|
|
463
487
|
] );
|
|
@@ -152,7 +152,8 @@ export const XYChartTooltip: React.FC< XYChartTooltipProps > = ( {
|
|
|
152
152
|
hasKeyboardSelection.current = true;
|
|
153
153
|
|
|
154
154
|
if ( mode === 'group' ) {
|
|
155
|
-
//
|
|
155
|
+
// visx merges each shown datum into the previous tooltip data, so start empty or a series dropped from `series` keeps its row.
|
|
156
|
+
closeTooltipNow();
|
|
156
157
|
series.forEach( ( s, index ) => {
|
|
157
158
|
if ( selectedIndex < s.data.length ) {
|
|
158
159
|
const datum = s.data[ selectedIndex ];
|
|
@@ -292,7 +293,7 @@ export const useKeyboardNavigation = ( {
|
|
|
292
293
|
const previousTotalPoints = useRef( totalPoints );
|
|
293
294
|
const previousVisibleSeriesKey = useRef( visibleSeriesKey );
|
|
294
295
|
|
|
295
|
-
// Hiding or showing a series
|
|
296
|
+
// Hiding or showing a series re-points every index. The count alone misses a
|
|
296
297
|
// swap that keeps it the same, such as hiding one series while revealing another of equal length.
|
|
297
298
|
// Clear rather than clamp once focus has left the chart: a new tooltip would pull focus back.
|
|
298
299
|
useEffect( () => {
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// Charts
|
|
2
2
|
export { AreaChart, AreaChartUnresponsive } from './charts/area-chart';
|
|
3
|
-
export {
|
|
3
|
+
export {
|
|
4
|
+
BarChart,
|
|
5
|
+
BarChartUnresponsive,
|
|
6
|
+
useBarColors,
|
|
7
|
+
useBarLegendItems,
|
|
8
|
+
} from './charts/bar-chart';
|
|
4
9
|
export { BarListChart, BarListChartUnresponsive } from './charts/bar-list-chart';
|
|
5
10
|
export { ConversionFunnelChart } from './charts/conversion-funnel-chart';
|
|
6
11
|
export { GeoChart, GeoChartUnresponsive } from './charts/geo-chart';
|
|
@@ -76,6 +81,7 @@ export type {
|
|
|
76
81
|
LegendShape,
|
|
77
82
|
LegendShapeLabel,
|
|
78
83
|
LegendShapeRenderProps,
|
|
84
|
+
BarChartLayout,
|
|
79
85
|
} from './types';
|
|
80
86
|
export type * from './visx/types';
|
|
81
87
|
export type { PieChartProps, PieChartRenderTooltipParams } from './charts/pie-chart';
|
|
@@ -109,7 +115,7 @@ export type { LegendProps, BaseLegendProps, ChartLegendOptions } from './compone
|
|
|
109
115
|
|
|
110
116
|
// Previously available via '@automattic/charts/bar-chart', '@automattic/charts/line-chart', etc.
|
|
111
117
|
export type { AreaChartProps } from './charts/area-chart';
|
|
112
|
-
export type { BarChartProps, BandHighlightSelection } from './charts/bar-chart';
|
|
118
|
+
export type { BarChartProps, BandHighlightSelection, BarLegendOptions } from './charts/bar-chart';
|
|
113
119
|
export type {
|
|
114
120
|
BarListChartProps,
|
|
115
121
|
RenderLabelProps,
|
|
@@ -371,6 +371,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
|
|
|
371
371
|
theme: providerTheme,
|
|
372
372
|
formatting,
|
|
373
373
|
getElementStyles,
|
|
374
|
+
backgroundColor: colorCache.background,
|
|
374
375
|
toggleSeriesVisibility,
|
|
375
376
|
setSeriesVisibility,
|
|
376
377
|
setChartHiddenSeries,
|
|
@@ -388,6 +389,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
|
|
|
388
389
|
providerTheme,
|
|
389
390
|
formatting,
|
|
390
391
|
getElementStyles,
|
|
392
|
+
colorCache.background,
|
|
391
393
|
toggleSeriesVisibility,
|
|
392
394
|
setSeriesVisibility,
|
|
393
395
|
setChartHiddenSeries,
|
|
@@ -5,7 +5,6 @@ import { useChartId } from '../hooks/use-chart-id';
|
|
|
5
5
|
import { useChartRegistration } from '../hooks/use-chart-registration';
|
|
6
6
|
import { useGlobalChartsContext } from '../hooks/use-global-charts-context';
|
|
7
7
|
import { createPaletteGenerator } from '../private/palette-generator';
|
|
8
|
-
import { defaultTheme } from '../themes';
|
|
9
8
|
import type { BaseLegendItem } from '../../../components/legend';
|
|
10
9
|
import type { SeriesData } from '../../../types';
|
|
11
10
|
import type { GlobalChartsContextValue } from '../types';
|
|
@@ -1168,12 +1167,9 @@ describe( 'ChartContext', () => {
|
|
|
1168
1167
|
legendShape: 'rect',
|
|
1169
1168
|
} );
|
|
1170
1169
|
|
|
1171
|
-
// Should get theme legend shape styles (not line styles), with the comparison bar
|
|
1172
|
-
// opacity layered on so the swatch matches the translucent comparison bar.
|
|
1173
1170
|
expect( styles.shapeStyles ).toEqual( {
|
|
1174
1171
|
fill: '#LEGEND1',
|
|
1175
1172
|
stroke: '#BORDER1',
|
|
1176
|
-
opacity: 0.5,
|
|
1177
1173
|
} );
|
|
1178
1174
|
} );
|
|
1179
1175
|
} );
|
|
@@ -2144,13 +2140,4 @@ describe( 'ChartContext', () => {
|
|
|
2144
2140
|
} );
|
|
2145
2141
|
} );
|
|
2146
2142
|
} );
|
|
2147
|
-
|
|
2148
|
-
describe( 'defaultTheme', () => {
|
|
2149
|
-
it( 'exposes default barChart comparison styles', () => {
|
|
2150
|
-
expect( defaultTheme.barChart.barStyles.comparison ).toEqual( {
|
|
2151
|
-
widthFactor: 1.5,
|
|
2152
|
-
opacity: 0.5,
|
|
2153
|
-
} );
|
|
2154
|
-
} );
|
|
2155
|
-
} );
|
|
2156
2143
|
} );
|
|
@@ -41,6 +41,8 @@ export interface GlobalChartsContextValue {
|
|
|
41
41
|
/** Locale and time zone every date the charts print is rendered in. */
|
|
42
42
|
formatting: ChartFormatting;
|
|
43
43
|
getElementStyles: ( params: GetElementStylesParams ) => ElementStyles;
|
|
44
|
+
/** The chart background as an opaque hex, for colors that must keep contrast against it. Optional so a hand-built context value still type-checks. */
|
|
45
|
+
backgroundColor?: string;
|
|
44
46
|
// Series visibility management shared by charts, legends, and programmatic controls.
|
|
45
47
|
toggleSeriesVisibility: ( chartId: string, seriesLabel: string ) => void;
|
|
46
48
|
setSeriesVisibility: ( chartId: string, seriesLabel: string, visible: boolean ) => void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { screen } from '@testing-library/react';
|
|
2
|
+
|
|
3
|
+
const NUMBER = /-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/gi;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The box the bar chart's selected ring outlines, read from its path.
|
|
7
|
+
*
|
|
8
|
+
* @return The ring's x, y, width and height.
|
|
9
|
+
*/
|
|
10
|
+
export const selectedBarRingBounds = () => {
|
|
11
|
+
const d = screen.getByTestId( 'bar-chart-selected-ring' ).getAttribute( 'd' ) ?? '';
|
|
12
|
+
const [ x, y, width, height ] = ( d.match( NUMBER ) ?? [] ).map( Number );
|
|
13
|
+
return { x, y, width, height };
|
|
14
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -306,14 +306,22 @@ export type SeriesData = {
|
|
|
306
306
|
|
|
307
307
|
/**
|
|
308
308
|
* Visual styling for a bar series of a given semantic type (e.g. 'comparison').
|
|
309
|
-
* `widthFactor` is the bar width relative to the primary bar slot (1.5 = 150%)
|
|
310
|
-
* `opacity` sets the shadow translucency.
|
|
309
|
+
* `widthFactor` is the bar width relative to the primary bar slot (1.5 = 150%).
|
|
311
310
|
*/
|
|
312
311
|
export type BarStyles = {
|
|
313
312
|
widthFactor?: number;
|
|
313
|
+
/**
|
|
314
|
+
* Draws the comparison bar translucent, in its primary's color, instead of the opaque tones the chart picks.
|
|
315
|
+
* @deprecated Comparison bars are opaque. Leave this unset; set the comparison series' `options.stroke` for a color of its own.
|
|
316
|
+
*/
|
|
314
317
|
opacity?: number;
|
|
315
318
|
};
|
|
316
319
|
|
|
320
|
+
/**
|
|
321
|
+
* How a bar chart places its series in a category. `grouped` puts each primary series in a slot of its own; `overlap` puts every primary in one slot and every comparison in a second, and overlaps the `group`s within each slot.
|
|
322
|
+
*/
|
|
323
|
+
export type BarChartLayout = 'grouped' | 'overlap';
|
|
324
|
+
|
|
317
325
|
export type MultipleDataPointsDate = {
|
|
318
326
|
label: string;
|
|
319
327
|
data: DataPointDate[];
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import styles from './pie-keyboard-navigation.module.scss';
|
|
2
|
-
|
|
3
|
-
interface SelectedSegmentRingProps {
|
|
4
|
-
/** The chart's id, which keeps the clip path unique on a page with several charts. */
|
|
5
|
-
chartId: string;
|
|
6
|
-
/** The selected segment's path, from the same arc generator that draws the segment. */
|
|
7
|
-
d: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Encodes `value` for an SVG id, losslessly so distinct chart ids never share a clip path.
|
|
12
|
-
*
|
|
13
|
-
* @param value - Any string.
|
|
14
|
-
* @return `value` with each character outside `[A-Za-z0-9-]` written as `_<hex code point>_`.
|
|
15
|
-
*/
|
|
16
|
-
export const toSvgId = ( value: string ) =>
|
|
17
|
-
Array.from( value, character =>
|
|
18
|
-
/[A-Za-z0-9-]/.test( character )
|
|
19
|
-
? character
|
|
20
|
-
: `_${ character.codePointAt( 0 )?.toString( 16 ) }_`
|
|
21
|
-
).join( '' );
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Marks the selected segment with a two-tone ring drawn inside its edge, so the SVG edge cannot cut it off.
|
|
25
|
-
*
|
|
26
|
-
* @param {SelectedSegmentRingProps} props - Component props
|
|
27
|
-
* @return {JSX.Element} The ring
|
|
28
|
-
*/
|
|
29
|
-
export const SelectedSegmentRing = ( { chartId, d }: SelectedSegmentRingProps ) => {
|
|
30
|
-
const clipId = `pie-selected-ring-clip-${ toSvgId( chartId ) }`;
|
|
31
|
-
const clipPath = `url(#${ clipId })`;
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<g pointerEvents="none" aria-hidden="true">
|
|
35
|
-
<clipPath id={ clipId }>
|
|
36
|
-
<path d={ d } />
|
|
37
|
-
</clipPath>
|
|
38
|
-
<path d={ d } clipPath={ clipPath } className={ styles[ 'selected-ring__contrast' ] } />
|
|
39
|
-
<path
|
|
40
|
-
d={ d }
|
|
41
|
-
clipPath={ clipPath }
|
|
42
|
-
className={ styles[ 'selected-ring__focus' ] }
|
|
43
|
-
data-testid="pie-selected-ring"
|
|
44
|
-
/>
|
|
45
|
-
</g>
|
|
46
|
-
);
|
|
47
|
-
};
|