@automattic/charts 3.2.0 → 4.1.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 +47 -0
- package/SECURITY.md +1 -0
- package/TOKENS.md +53 -58
- package/dist/index.cjs +1483 -664
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +93 -29
- package/dist/index.d.cts +233 -247
- package/dist/index.d.ts +233 -247
- package/dist/index.js +1484 -667
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/charts/area-chart/area-chart.tsx +39 -25
- package/src/charts/area-chart/test/area-chart.test.tsx +4 -33
- package/src/charts/area-chart/test/tick-placement.test.tsx +55 -0
- package/src/charts/area-chart/test/tooltip-resolution-branches.test.tsx +86 -0
- package/src/charts/area-chart/test/tooltip-resolution.test.tsx +69 -0
- package/src/charts/area-chart/types.ts +4 -7
- package/src/charts/bar-chart/bar-chart.tsx +2 -1
- package/src/charts/bar-chart/private/test/use-bar-chart-options-formatting.test.tsx +69 -0
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +27 -15
- package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -0
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +33 -23
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +138 -182
- package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +46 -0
- package/src/charts/geo-chart/geo-chart.module.scss +1 -0
- package/src/charts/geo-chart/geo-chart.tsx +5 -10
- package/src/charts/geo-chart/test/geo-chart.test.tsx +2 -2
- package/src/charts/heatmap-chart/heatmap-chart.module.scss +52 -16
- package/src/charts/heatmap-chart/heatmap-chart.tsx +89 -37
- package/src/charts/heatmap-chart/index.ts +2 -1
- package/src/charts/heatmap-chart/private/build-calendar-data.ts +126 -89
- package/src/charts/heatmap-chart/private/civil-day.ts +174 -0
- package/src/charts/heatmap-chart/private/heatmap-legend.tsx +11 -4
- package/src/charts/heatmap-chart/private/index.ts +1 -1
- package/src/charts/heatmap-chart/private/use-heatmap-colors.ts +5 -1
- package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +3 -0
- package/src/charts/heatmap-chart/test/civil-day.test.ts +70 -0
- package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +142 -12
- package/src/charts/heatmap-chart/test/use-calendar-heatmap-data.test.tsx +118 -0
- package/src/charts/heatmap-chart/test/use-heatmap-colors.test.ts +8 -0
- package/src/charts/heatmap-chart/test/zoned-calendar-data.test.ts +248 -0
- package/src/charts/heatmap-chart/types.ts +8 -2
- package/src/charts/heatmap-chart/use-calendar-heatmap-data.ts +42 -0
- package/src/charts/leaderboard-chart/hooks/use-fitted-row-count.ts +2 -2
- package/src/charts/leaderboard-chart/hooks/use-leaderboard-legend-items.ts +3 -5
- package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +31 -19
- package/src/charts/leaderboard-chart/leaderboard-chart.tsx +27 -17
- package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +14 -0
- package/src/charts/leaderboard-chart/test/use-leaderboard-legend-items.test.tsx +2 -9
- package/src/charts/leaderboard-chart/types.ts +2 -2
- package/src/charts/line-chart/line-chart.module.scss +10 -18
- package/src/charts/line-chart/line-chart.tsx +128 -26
- package/src/charts/line-chart/private/line-chart-annotation.tsx +18 -19
- package/src/charts/line-chart/test/crosshair-style-types.test.ts +31 -0
- package/src/charts/line-chart/test/default-tooltip-formatting.test.tsx +74 -0
- package/src/charts/line-chart/test/line-chart.test.tsx +174 -41
- package/src/charts/line-chart/test/tick-placement.test.tsx +84 -0
- package/src/charts/line-chart/test/tooltip-resolution.test.tsx +140 -0
- package/src/charts/line-chart/test/zoned-date-string.test.tsx +37 -0
- package/src/charts/line-chart/types.ts +17 -3
- package/src/charts/pie-chart/pie-chart.module.scss +13 -1
- package/src/charts/pie-chart/pie-chart.tsx +30 -40
- package/src/charts/pie-chart/test/pie-chart.test.tsx +59 -12
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +8 -3
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +23 -37
- package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +50 -0
- package/src/charts/private/chart-layout/chart-layout.module.scss +3 -2
- package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +1 -1
- package/src/charts/private/test/axis-formatting.test.tsx +85 -0
- package/src/charts/private/test/time-axis-formatting.test.ts +109 -0
- package/src/charts/private/test/time-axis-options.test.ts +267 -0
- package/src/charts/private/test/time-axis-tick-invariants.test.ts +153 -0
- package/src/charts/private/test/time-axis-tick-values.test.ts +280 -0
- package/src/charts/private/test/time-axis.test.ts +9 -69
- package/src/charts/private/time-axis-options.ts +106 -0
- package/src/charts/private/time-axis.ts +318 -136
- package/src/charts/private/x-zoom/x-zoom.module.scss +3 -3
- package/src/charts/private/xy-plot/xy-plot.module.scss +8 -0
- package/src/charts/sparkline/test/sparkline.test.tsx +29 -56
- package/src/charts/sparkline/types.ts +1 -1
- package/src/components/legend/private/base-legend.module.scss +9 -5
- package/src/components/tooltip/accessible-tooltip.tsx +21 -17
- package/src/components/tooltip/base-tooltip.module.scss +3 -3
- package/src/components/tooltip/private/bounded-tooltip.tsx +227 -0
- package/src/components/tooltip/test/accessible-tooltip.test.tsx +30 -4
- package/src/components/tooltip/test/bounded-tooltip.test.tsx +225 -0
- package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +379 -0
- package/src/components/tooltip/xy-chart-tooltip.tsx +326 -0
- package/src/components/trend-indicator/trend-indicator.module.scss +2 -2
- package/src/hooks/test/use-chart-data-transform-zone.test.tsx +36 -0
- package/src/hooks/test/use-chart-margin.test.tsx +173 -3
- package/src/hooks/test/use-xychart-theme.test.tsx +16 -3
- package/src/hooks/use-chart-data-transform.ts +10 -3
- package/src/hooks/use-chart-margin.tsx +54 -9
- package/src/hooks/use-xychart-theme.ts +25 -9
- package/src/index.ts +8 -0
- package/src/providers/chart-context/global-charts-provider.tsx +37 -38
- package/src/providers/chart-context/hooks/use-chart-formatting.ts +22 -0
- package/src/providers/chart-context/index.ts +1 -0
- package/src/providers/chart-context/private/catalog-pointers.ts +42 -0
- package/src/providers/chart-context/private/series-palette.ts +3 -13
- package/src/providers/chart-context/test/{sparse-theme.test.ts → catalog-pointers.test.ts} +22 -10
- package/src/providers/chart-context/test/chart-context.test.tsx +177 -792
- package/src/providers/chart-context/test/use-global-charts-theme.test.tsx +7 -25
- package/src/providers/chart-context/themes.ts +7 -52
- package/src/providers/chart-context/types.ts +3 -0
- package/src/styles/chart-scope.scss +39 -62
- package/src/styles/test/chart-scope.test.ts +19 -47
- package/src/test-utils/series-fixtures.ts +31 -0
- package/src/test-utils/zoned-parsing-suite.ts +90 -0
- package/src/types.ts +79 -166
- package/src/utils/bucket-info.ts +87 -0
- package/src/utils/color-utils.ts +0 -27
- package/src/utils/date-formatting.ts +157 -0
- package/src/utils/date-parsing.ts +103 -52
- package/src/utils/get-edge-tick-widths.ts +33 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/test/bucket-info.test.ts +185 -0
- package/src/utils/test/color-utils.test.ts +1 -48
- package/src/utils/test/date-formatting.test.ts +89 -0
- package/src/utils/test/date-parsing.auckland.test.ts +6 -0
- package/src/utils/test/date-parsing.santiago.test.ts +6 -0
- package/src/utils/test/date-parsing.test.ts +16 -1
- package/src/utils/test/get-edge-tick-widths.test.ts +55 -0
- package/src/utils/test/get-styles.test.ts +10 -7
- package/src/utils/test/merge-themes.test.ts +18 -46
- package/src/utils/warn-once.ts +23 -0
- package/src/visx/types.ts +56 -5
- package/src/hooks/test/use-xychart-theme-provider-override.test.tsx +0 -28
- package/src/providers/chart-context/private/theme-override-vars.ts +0 -131
- package/src/providers/chart-context/private/with-catalog-pointers.ts +0 -93
- package/src/providers/chart-context/test/theme-override-vars-guard.test.tsx +0 -29
- package/src/providers/chart-context/test/theme-override-vars.test.ts +0 -183
- package/src/providers/chart-context/test/with-catalog-pointers.test.ts +0 -104
- package/src/styles/test/wpds-fallbacks.test.ts +0 -131
|
@@ -352,24 +352,21 @@ describe( 'HeatmapChart', () => {
|
|
|
352
352
|
expect( grid.style.getPropertyValue( '--a8c-charts-color-heatmap-primary' ) ).toBe( '#abcdef' );
|
|
353
353
|
} );
|
|
354
354
|
|
|
355
|
-
test( '
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
</GlobalChartsProvider>
|
|
360
|
-
);
|
|
361
|
-
const grid = screen.getByRole( 'grid', { name: /heatmap/i } );
|
|
362
|
-
expect( grid.style.getPropertyValue( '--a8c-charts-color-heatmap-primary' ) ).toBe( '#0a0b0c' );
|
|
363
|
-
} );
|
|
355
|
+
test( 'falls back to the first palette slot when no primaryColor prop is set', () => {
|
|
356
|
+
const scope = document.createElement( 'div' );
|
|
357
|
+
scope.style.setProperty( '--a8c-charts-color-series-1', '#0a0b0c' );
|
|
358
|
+
document.body.appendChild( scope );
|
|
364
359
|
|
|
365
|
-
test( 'falls back to the palette colors[0] when no prop or theme primaryColor is set', () => {
|
|
366
360
|
render(
|
|
367
|
-
<GlobalChartsProvider
|
|
361
|
+
<GlobalChartsProvider>
|
|
368
362
|
<HeatmapChart width={ 500 } height={ 300 } data={ data } />
|
|
369
|
-
</GlobalChartsProvider
|
|
363
|
+
</GlobalChartsProvider>,
|
|
364
|
+
{ container: scope }
|
|
370
365
|
);
|
|
371
366
|
const grid = screen.getByRole( 'grid', { name: /heatmap/i } );
|
|
372
367
|
expect( grid.style.getPropertyValue( '--a8c-charts-color-heatmap-primary' ) ).toBe( '#0a0b0c' );
|
|
368
|
+
|
|
369
|
+
document.body.removeChild( scope );
|
|
373
370
|
} );
|
|
374
371
|
|
|
375
372
|
test( 'the unresponsive export pins explicit width and height', () => {
|
|
@@ -393,3 +390,136 @@ describe( 'HeatmapChart', () => {
|
|
|
393
390
|
expect( chart ).not.toHaveStyle( { height: '300px' } );
|
|
394
391
|
} );
|
|
395
392
|
} );
|
|
393
|
+
|
|
394
|
+
// Chart container at (100, 50); the tooltip box measures 120x40. Everything
|
|
395
|
+
// else the charts measure (wrapper, clipping lookups) reports the container.
|
|
396
|
+
const mockRects = () =>
|
|
397
|
+
jest.spyOn( Element.prototype, 'getBoundingClientRect' ).mockImplementation( function (
|
|
398
|
+
this: Element
|
|
399
|
+
) {
|
|
400
|
+
const box = this.classList.contains( 'visx-tooltip' );
|
|
401
|
+
return {
|
|
402
|
+
left: box ? 0 : 100,
|
|
403
|
+
top: box ? 0 : 50,
|
|
404
|
+
width: box ? 120 : 400,
|
|
405
|
+
height: box ? 40 : 300,
|
|
406
|
+
right: box ? 120 : 500,
|
|
407
|
+
bottom: box ? 40 : 350,
|
|
408
|
+
x: box ? 0 : 100,
|
|
409
|
+
y: box ? 0 : 50,
|
|
410
|
+
toJSON: () => ( {} ),
|
|
411
|
+
} as DOMRect;
|
|
412
|
+
} );
|
|
413
|
+
|
|
414
|
+
describe( 'HeatmapChart keyboard tooltip', () => {
|
|
415
|
+
test( 'opens on the selected cell without row labels', async () => {
|
|
416
|
+
renderChart( { withTooltips: true } );
|
|
417
|
+
const grid = screen.getByRole( 'grid', { name: /heatmap/i } );
|
|
418
|
+
const user = userEvent.setup();
|
|
419
|
+
|
|
420
|
+
grid.focus();
|
|
421
|
+
await user.keyboard( '{ArrowDown}' );
|
|
422
|
+
|
|
423
|
+
expect( within( screen.getByRole( 'tooltip' ) ).getByText( '1' ) ).toBeInTheDocument();
|
|
424
|
+
} );
|
|
425
|
+
} );
|
|
426
|
+
|
|
427
|
+
describe( 'HeatmapChart tooltip position', () => {
|
|
428
|
+
afterEach( () => {
|
|
429
|
+
jest.restoreAllMocks();
|
|
430
|
+
} );
|
|
431
|
+
|
|
432
|
+
test( 'places the box relative to the chart root, at the pointer plus the offsets', async () => {
|
|
433
|
+
mockRects();
|
|
434
|
+
renderChart( { withTooltips: true, rowLabels: [ 'Mon', 'Tue', 'Wed' ] } );
|
|
435
|
+
|
|
436
|
+
await userEvent.setup().pointer( {
|
|
437
|
+
target: screen.getAllByTestId( 'heatmap-cell' )[ 0 ],
|
|
438
|
+
coords: { clientX: 180, clientY: 140 },
|
|
439
|
+
} );
|
|
440
|
+
|
|
441
|
+
// (180, 140) is (80, 90) inside the root; the box adds 10px each way.
|
|
442
|
+
await expect( screen.findByRole( 'tooltip' ) ).resolves.toBeInTheDocument();
|
|
443
|
+
expect( screen.getByTestId( 'bounded-tooltip' ) ).toHaveStyle( {
|
|
444
|
+
transform: 'translate(90px, 100px)',
|
|
445
|
+
} );
|
|
446
|
+
} );
|
|
447
|
+
} );
|
|
448
|
+
|
|
449
|
+
describe( 'HeatmapChart summary column', () => {
|
|
450
|
+
const withTotals: HeatmapColumn[] = [
|
|
451
|
+
...data,
|
|
452
|
+
{ label: 'Total', summary: true, data: [ { value: 400 }, { value: 200 }, { value: null } ] },
|
|
453
|
+
];
|
|
454
|
+
|
|
455
|
+
test( 'keeps the summary column out of the color scale', () => {
|
|
456
|
+
renderChart( { data: withTotals } );
|
|
457
|
+
// 4 is still the data maximum, so it keeps full intensity.
|
|
458
|
+
expect(
|
|
459
|
+
screen
|
|
460
|
+
.getByRole( 'gridcell', { name: 'W2: 4' } )
|
|
461
|
+
.style.getPropertyValue( '--a8c-charts-heatmap-cell-intensity' )
|
|
462
|
+
).toBe( '1' );
|
|
463
|
+
expect(
|
|
464
|
+
screen
|
|
465
|
+
.getByRole( 'gridcell', { name: 'Total: 400' } )
|
|
466
|
+
.style.getPropertyValue( '--a8c-charts-heatmap-cell-intensity' )
|
|
467
|
+
).toBe( '' );
|
|
468
|
+
} );
|
|
469
|
+
|
|
470
|
+
test( 'draws every summary cell, printing its figure even in compact mode', () => {
|
|
471
|
+
renderChart( { data: withTotals, compact: true } );
|
|
472
|
+
expect( screen.getAllByTestId( 'heatmap-cell-summary' ) ).toHaveLength( 3 );
|
|
473
|
+
expect( screen.getByRole( 'gridcell', { name: 'Total: 400' } ) ).toHaveTextContent( '400' );
|
|
474
|
+
expect( screen.getByRole( 'gridcell', { name: 'Total: No data' } ) ).toBeEmptyDOMElement();
|
|
475
|
+
expect( screen.getByRole( 'grid' ) ).toHaveAttribute( 'aria-colcount', '3' );
|
|
476
|
+
} );
|
|
477
|
+
|
|
478
|
+
test( 'sets a summary apart from the data on either side, but not from another summary', () => {
|
|
479
|
+
renderChart( {
|
|
480
|
+
data: [
|
|
481
|
+
{ label: 'Lead', summary: true, data: [ { value: 7 }, { value: 8 }, { value: 9 } ] },
|
|
482
|
+
...withTotals,
|
|
483
|
+
{ label: 'Mean', summary: true, data: [ { value: 2 }, { value: 1 }, { value: 3 } ] },
|
|
484
|
+
],
|
|
485
|
+
} );
|
|
486
|
+
|
|
487
|
+
const lead = screen.getByRole( 'gridcell', { name: 'Lead: 7' } );
|
|
488
|
+
expect( lead ).toHaveClass( 'heatmap-chart__gap-end' );
|
|
489
|
+
expect( lead ).not.toHaveClass( 'heatmap-chart__gap-start' );
|
|
490
|
+
|
|
491
|
+
const total = screen.getByRole( 'gridcell', { name: 'Total: 400' } );
|
|
492
|
+
expect( total ).toHaveClass( 'heatmap-chart__gap-start' );
|
|
493
|
+
expect( total ).not.toHaveClass( 'heatmap-chart__gap-end' );
|
|
494
|
+
|
|
495
|
+
const mean = screen.getByRole( 'gridcell', { name: 'Mean: 2' } );
|
|
496
|
+
expect( mean ).not.toHaveClass( 'heatmap-chart__gap-start' );
|
|
497
|
+
expect( mean ).not.toHaveClass( 'heatmap-chart__gap-end' );
|
|
498
|
+
expect( screen.getByRole( 'gridcell', { name: 'W1: 1' } ) ).not.toHaveClass(
|
|
499
|
+
'heatmap-chart__gap-start'
|
|
500
|
+
);
|
|
501
|
+
} );
|
|
502
|
+
|
|
503
|
+
test( 'keeps the summary track content-sized when the data tracks are capped', () => {
|
|
504
|
+
renderChart( { data: withTotals, maxCellWidth: 32 } );
|
|
505
|
+
|
|
506
|
+
expect( screen.getByRole( 'grid', { name: /heatmap/i } ) ).toHaveStyle( {
|
|
507
|
+
gridTemplateColumns: 'auto minmax(0px, 32px) minmax(0px, 32px) minmax(auto, max-content)',
|
|
508
|
+
} );
|
|
509
|
+
} );
|
|
510
|
+
|
|
511
|
+
test( 'reaches the summary column by keyboard, with its tooltip', async () => {
|
|
512
|
+
renderChart( { data: withTotals, withTooltips: true } );
|
|
513
|
+
const grid = screen.getByRole( 'grid', { name: /heatmap/i } );
|
|
514
|
+
const user = userEvent.setup();
|
|
515
|
+
|
|
516
|
+
grid.focus();
|
|
517
|
+
await user.keyboard( '{ArrowDown}{ArrowRight}{ArrowRight}' );
|
|
518
|
+
|
|
519
|
+
expect( grid ).toHaveAttribute(
|
|
520
|
+
'aria-activedescendant',
|
|
521
|
+
expect.stringMatching( /-cell-2-0$/ )
|
|
522
|
+
);
|
|
523
|
+
expect( within( screen.getByRole( 'tooltip' ) ).getByText( '400' ) ).toBeInTheDocument();
|
|
524
|
+
} );
|
|
525
|
+
} );
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment <rootDir>/tests/environment-los-angeles.mjs
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Los Angeles runs behind UTC, the half `zoned-calendar-data` cannot cover: a label
|
|
6
|
+
// formatter that stops reading UTC renders the proxy on the previous day only here.
|
|
7
|
+
import { render } from '@testing-library/react';
|
|
8
|
+
import { GlobalChartsProvider } from '../../../providers';
|
|
9
|
+
import { useCalendarHeatmapData } from '../use-calendar-heatmap-data';
|
|
10
|
+
import type { DataPointDate } from '../../../types';
|
|
11
|
+
import type { CalendarHeatmapOptions, CalendarHeatmapResult } from '../private';
|
|
12
|
+
|
|
13
|
+
// 2026-08-03 08:00 in Asia/Tokyo, 2026-08-02 16:00 in America/Los_Angeles.
|
|
14
|
+
const series: DataPointDate[] = [ { date: new Date( '2026-08-02T23:00:00Z' ), value: 7 } ];
|
|
15
|
+
|
|
16
|
+
// Aug 3 in UTC, Aug 2 in this file's zone, so a bucket tells the two apart.
|
|
17
|
+
const runtimeVsUtc: DataPointDate[] = [ { date: new Date( '2026-08-03T05:00:00Z' ), value: 7 } ];
|
|
18
|
+
|
|
19
|
+
describe( 'useCalendarHeatmapData', () => {
|
|
20
|
+
let result: CalendarHeatmapResult;
|
|
21
|
+
let renders = 0;
|
|
22
|
+
|
|
23
|
+
const Calendar = ( {
|
|
24
|
+
options,
|
|
25
|
+
points = series,
|
|
26
|
+
}: {
|
|
27
|
+
options?: CalendarHeatmapOptions;
|
|
28
|
+
points?: DataPointDate[];
|
|
29
|
+
} ) => {
|
|
30
|
+
result = useCalendarHeatmapData( points, { weekStartsOn: 1, ...options } );
|
|
31
|
+
renders++;
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
beforeEach( () => {
|
|
36
|
+
renders = 0;
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
const bucketedLabels = () =>
|
|
40
|
+
result.data
|
|
41
|
+
.flatMap( column => column.data )
|
|
42
|
+
.filter( cell => cell.value !== null )
|
|
43
|
+
.map( cell => cell.label );
|
|
44
|
+
|
|
45
|
+
it( "takes the provider's locale and time zone", () => {
|
|
46
|
+
render(
|
|
47
|
+
<GlobalChartsProvider locale="de-DE" timeZone="Asia/Tokyo">
|
|
48
|
+
<Calendar />
|
|
49
|
+
</GlobalChartsProvider>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect( bucketedLabels() ).toEqual( [ 'Mo., 3. Aug. 2026' ] );
|
|
53
|
+
expect( result.rowLabels ).toEqual( [ 'Mo', '', 'Mi', '', 'Fr', '', '' ] );
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
it( 'lets an option win over the provider', () => {
|
|
57
|
+
render(
|
|
58
|
+
<GlobalChartsProvider locale="de-DE" timeZone="Asia/Tokyo">
|
|
59
|
+
<Calendar options={ { timeZone: 'America/Los_Angeles' } } />
|
|
60
|
+
</GlobalChartsProvider>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect( bucketedLabels() ).toEqual( [ 'So., 2. Aug. 2026' ] );
|
|
64
|
+
} );
|
|
65
|
+
|
|
66
|
+
it( 'reads an explicit undefined as unset rather than as an override', () => {
|
|
67
|
+
render(
|
|
68
|
+
<GlobalChartsProvider locale="de-DE" timeZone="Asia/Tokyo">
|
|
69
|
+
<Calendar options={ { locale: undefined, timeZone: undefined } } />
|
|
70
|
+
</GlobalChartsProvider>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect( bucketedLabels() ).toEqual( [ 'Mo., 3. Aug. 2026' ] );
|
|
74
|
+
} );
|
|
75
|
+
|
|
76
|
+
it( 'falls back to the runtime with no provider around it', () => {
|
|
77
|
+
render( <Calendar points={ runtimeVsUtc } /> );
|
|
78
|
+
|
|
79
|
+
expect( result.rowLabels ).toEqual( [ 'Mon', '', 'Wed', '', 'Fri', '', '' ] );
|
|
80
|
+
// rowLabels only prove the locale fell back; the bucket proves the zone did.
|
|
81
|
+
expect( bucketedLabels() ).toEqual( [ 'Sun, Aug 2, 2026' ] );
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
it( 'holds its result across a re-render that rebuilds the options object', () => {
|
|
85
|
+
const { rerender } = render(
|
|
86
|
+
<GlobalChartsProvider locale="de-DE" timeZone="Asia/Tokyo">
|
|
87
|
+
<Calendar options={ { gridSpan: { start: '2026-07-06' } } } />
|
|
88
|
+
</GlobalChartsProvider>
|
|
89
|
+
);
|
|
90
|
+
const first = result;
|
|
91
|
+
|
|
92
|
+
rerender(
|
|
93
|
+
<GlobalChartsProvider locale="de-DE" timeZone="Asia/Tokyo">
|
|
94
|
+
<Calendar options={ { gridSpan: { start: '2026-07-06' } } } />
|
|
95
|
+
</GlobalChartsProvider>
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect( renders ).toBeGreaterThan( 1 );
|
|
99
|
+
expect( result ).toBe( first );
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
it( 'rebuilds when the provider changes zone', () => {
|
|
103
|
+
const { rerender } = render(
|
|
104
|
+
<GlobalChartsProvider locale="de-DE" timeZone="Asia/Tokyo">
|
|
105
|
+
<Calendar />
|
|
106
|
+
</GlobalChartsProvider>
|
|
107
|
+
);
|
|
108
|
+
expect( bucketedLabels() ).toEqual( [ 'Mo., 3. Aug. 2026' ] );
|
|
109
|
+
|
|
110
|
+
rerender(
|
|
111
|
+
<GlobalChartsProvider locale="de-DE" timeZone="America/Los_Angeles">
|
|
112
|
+
<Calendar />
|
|
113
|
+
</GlobalChartsProvider>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
expect( bucketedLabels() ).toEqual( [ 'So., 2. Aug. 2026' ] );
|
|
117
|
+
} );
|
|
118
|
+
} );
|
|
@@ -11,6 +11,14 @@ describe( 'getValueExtent', () => {
|
|
|
11
11
|
expect( getValueExtent( data ) ).toEqual( [ 0, 20 ] );
|
|
12
12
|
} );
|
|
13
13
|
|
|
14
|
+
test( 'leaves summary columns out of the extent', () => {
|
|
15
|
+
const withTotals: HeatmapColumn[] = [
|
|
16
|
+
...data,
|
|
17
|
+
{ label: 'Total', summary: true, data: [ { value: 500 }, { value: 900 }, { value: null } ] },
|
|
18
|
+
];
|
|
19
|
+
expect( getValueExtent( withTotals ) ).toEqual( [ 0, 20 ] );
|
|
20
|
+
} );
|
|
21
|
+
|
|
14
22
|
test( 'returns [0, 0] for all-empty data', () => {
|
|
15
23
|
expect( getValueExtent( [ { data: [ { value: null } ] } ] ) ).toEqual( [ 0, 0 ] );
|
|
16
24
|
} );
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment <rootDir>/tests/environment-chatham.mjs
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Chatham runs ahead of UTC. A worker behind it renders a UTC-midnight proxy on
|
|
6
|
+
// its own day, so it cannot tell the grid's UTC arithmetic from local arithmetic.
|
|
7
|
+
import { buildCalendarHeatmapData } from '../private/build-calendar-data';
|
|
8
|
+
import type { DataPointDate } from '../../../types';
|
|
9
|
+
|
|
10
|
+
const labelsCarryingValues = ( data: ReturnType< typeof buildCalendarHeatmapData >[ 'data' ] ) =>
|
|
11
|
+
data.flatMap( column => column.data ).filter( cell => cell.value !== null );
|
|
12
|
+
|
|
13
|
+
// 2026-08-03 08:00 in Asia/Tokyo, 2026-08-02 16:00 in America/Los_Angeles.
|
|
14
|
+
const nearMidnight: DataPointDate[] = [ { date: new Date( '2026-08-02T23:00:00Z' ), value: 7 } ];
|
|
15
|
+
|
|
16
|
+
describe( 'buildCalendarHeatmapData bucketing in a host time zone', () => {
|
|
17
|
+
test( "buckets an instant on the host's day, not the runtime's", () => {
|
|
18
|
+
const tokyo = buildCalendarHeatmapData( nearMidnight, {
|
|
19
|
+
weekStartsOn: 1,
|
|
20
|
+
timeZone: 'Asia/Tokyo',
|
|
21
|
+
} );
|
|
22
|
+
const losAngeles = buildCalendarHeatmapData( nearMidnight, {
|
|
23
|
+
weekStartsOn: 1,
|
|
24
|
+
timeZone: 'America/Los_Angeles',
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
expect( labelsCarryingValues( tokyo.data ).map( cell => cell.label ) ).toEqual( [
|
|
28
|
+
'Mon, Aug 3, 2026',
|
|
29
|
+
] );
|
|
30
|
+
expect( labelsCarryingValues( losAngeles.data ).map( cell => cell.label ) ).toEqual( [
|
|
31
|
+
'Sun, Aug 2, 2026',
|
|
32
|
+
] );
|
|
33
|
+
} );
|
|
34
|
+
|
|
35
|
+
test( 'places the grid on the host zone week, so the two zones differ by a column', () => {
|
|
36
|
+
// Aug 2 2026 is a Sunday and Aug 3 a Monday, so a Monday week start puts the
|
|
37
|
+
// two readings in different weeks entirely.
|
|
38
|
+
const tokyo = buildCalendarHeatmapData( nearMidnight, {
|
|
39
|
+
weekStartsOn: 1,
|
|
40
|
+
timeZone: 'Asia/Tokyo',
|
|
41
|
+
} );
|
|
42
|
+
|
|
43
|
+
expect( tokyo.data[ 0 ].data[ 0 ].value ).toBe( 7 );
|
|
44
|
+
expect( tokyo.data[ 0 ].data[ 6 ].hidden ).toBe( true );
|
|
45
|
+
} );
|
|
46
|
+
|
|
47
|
+
test( 'a UTC offset works where an IANA name would', () => {
|
|
48
|
+
const { data } = buildCalendarHeatmapData( nearMidnight, {
|
|
49
|
+
weekStartsOn: 1,
|
|
50
|
+
timeZone: '+09:00',
|
|
51
|
+
} );
|
|
52
|
+
|
|
53
|
+
expect( labelsCarryingValues( data ).map( cell => cell.label ) ).toEqual( [
|
|
54
|
+
'Mon, Aug 3, 2026',
|
|
55
|
+
] );
|
|
56
|
+
} );
|
|
57
|
+
} );
|
|
58
|
+
|
|
59
|
+
describe( 'buildCalendarHeatmapData date semantics', () => {
|
|
60
|
+
test( 'a bare yyyy-MM-dd is a calendar day, and no zone moves it', () => {
|
|
61
|
+
const series: DataPointDate[] = [ { dateString: '2024-01-03', value: 5 } ];
|
|
62
|
+
|
|
63
|
+
for ( const timeZone of [ undefined, 'Asia/Tokyo', 'America/Los_Angeles' ] ) {
|
|
64
|
+
const { data } = buildCalendarHeatmapData( series, { weekStartsOn: 1, timeZone } );
|
|
65
|
+
expect( labelsCarryingValues( data ).map( cell => cell.label ) ).toEqual( [
|
|
66
|
+
'Wed, Jan 3, 2024',
|
|
67
|
+
] );
|
|
68
|
+
}
|
|
69
|
+
} );
|
|
70
|
+
|
|
71
|
+
test( 'a datetime with no offset keeps the day it was written with', () => {
|
|
72
|
+
const series: DataPointDate[] = [ { dateString: '2024-01-03T23:30:00', value: 5 } ];
|
|
73
|
+
const { data } = buildCalendarHeatmapData( series, {
|
|
74
|
+
weekStartsOn: 1,
|
|
75
|
+
timeZone: 'Pacific/Kiritimati',
|
|
76
|
+
} );
|
|
77
|
+
|
|
78
|
+
expect( labelsCarryingValues( data ).map( cell => cell.label ) ).toEqual( [
|
|
79
|
+
'Wed, Jan 3, 2024',
|
|
80
|
+
] );
|
|
81
|
+
} );
|
|
82
|
+
|
|
83
|
+
test( 'a string carrying an offset is an instant, and the host zone re-dates it', () => {
|
|
84
|
+
const series: DataPointDate[] = [ { dateString: '2024-01-03T23:30:00Z', value: 5 } ];
|
|
85
|
+
const { data } = buildCalendarHeatmapData( series, {
|
|
86
|
+
weekStartsOn: 1,
|
|
87
|
+
timeZone: 'Asia/Tokyo',
|
|
88
|
+
} );
|
|
89
|
+
|
|
90
|
+
expect( labelsCarryingValues( data ).map( cell => cell.label ) ).toEqual( [
|
|
91
|
+
'Thu, Jan 4, 2024',
|
|
92
|
+
] );
|
|
93
|
+
} );
|
|
94
|
+
|
|
95
|
+
test( 'drops an instant no four-digit day key can hold', () => {
|
|
96
|
+
// `Date` reaches year 275760; a wider key would also sort before every other.
|
|
97
|
+
const outOfRange = [ { date: new Date( 1e15 ), value: 3 } ];
|
|
98
|
+
|
|
99
|
+
expect( buildCalendarHeatmapData( outOfRange ) ).toEqual( { data: [], rowLabels: [] } );
|
|
100
|
+
expect(
|
|
101
|
+
labelsCarryingValues(
|
|
102
|
+
buildCalendarHeatmapData( [ ...outOfRange, { dateString: '2024-01-03', value: 5 } ], {
|
|
103
|
+
weekStartsOn: 1,
|
|
104
|
+
} ).data
|
|
105
|
+
).map( cell => cell.label )
|
|
106
|
+
).toEqual( [ 'Wed, Jan 3, 2024' ] );
|
|
107
|
+
expect( console ).toHaveWarned();
|
|
108
|
+
} );
|
|
109
|
+
|
|
110
|
+
test( 'falls through to dateString when date is an Invalid Date', () => {
|
|
111
|
+
const { data } = buildCalendarHeatmapData(
|
|
112
|
+
[ { date: new Date( 'not a date' ), dateString: '2024-01-03', value: 5 } ],
|
|
113
|
+
{ weekStartsOn: 1 }
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
expect( labelsCarryingValues( data ).map( cell => cell.label ) ).toEqual( [
|
|
117
|
+
'Wed, Jan 3, 2024',
|
|
118
|
+
] );
|
|
119
|
+
} );
|
|
120
|
+
|
|
121
|
+
test( 'drops a written date that names no real day', () => {
|
|
122
|
+
expect( buildCalendarHeatmapData( [ { dateString: '2024-02-30', value: 5 } ] ) ).toEqual( {
|
|
123
|
+
data: [],
|
|
124
|
+
rowLabels: [],
|
|
125
|
+
} );
|
|
126
|
+
expect( console ).toHaveWarned();
|
|
127
|
+
} );
|
|
128
|
+
} );
|
|
129
|
+
|
|
130
|
+
describe( 'buildCalendarHeatmapData grid bounds are compared as calendar days', () => {
|
|
131
|
+
// Fri Jan 5 2024, 14:00 UTC: a time of day must not make a bound look earlier than
|
|
132
|
+
// the series and widen the grid.
|
|
133
|
+
const afternoon = [ { date: new Date( '2024-01-05T14:00:00Z' ), value: 5 } ];
|
|
134
|
+
|
|
135
|
+
test( 'a bound equal to the series start does not widen the grid', () => {
|
|
136
|
+
const { data } = buildCalendarHeatmapData( afternoon, {
|
|
137
|
+
weekStartsOn: 1,
|
|
138
|
+
timeZone: 'UTC',
|
|
139
|
+
gridSpan: { start: '2024-01-05' },
|
|
140
|
+
} );
|
|
141
|
+
|
|
142
|
+
// Mon Jan 1 to Thu Jan 4 precede the bound: the ragged edge, not filler.
|
|
143
|
+
expect( data[ 0 ].data.slice( 0, 4 ).map( cell => cell.hidden ) ).toEqual( [
|
|
144
|
+
true,
|
|
145
|
+
true,
|
|
146
|
+
true,
|
|
147
|
+
true,
|
|
148
|
+
] );
|
|
149
|
+
expect( data[ 0 ].data.slice( 0, 4 ).some( cell => cell.placeholder ) ).toBe( false );
|
|
150
|
+
} );
|
|
151
|
+
|
|
152
|
+
test( 'a bound genuinely earlier still widens it', () => {
|
|
153
|
+
const { data } = buildCalendarHeatmapData( afternoon, {
|
|
154
|
+
weekStartsOn: 1,
|
|
155
|
+
timeZone: 'UTC',
|
|
156
|
+
gridSpan: { start: '2024-01-04' },
|
|
157
|
+
} );
|
|
158
|
+
|
|
159
|
+
expect( data[ 0 ].data.slice( 0, 4 ).every( cell => cell.placeholder === true ) ).toBe( true );
|
|
160
|
+
} );
|
|
161
|
+
|
|
162
|
+
test( 'a bound genuinely later widens it too', () => {
|
|
163
|
+
const { data } = buildCalendarHeatmapData( afternoon, {
|
|
164
|
+
weekStartsOn: 1,
|
|
165
|
+
timeZone: 'UTC',
|
|
166
|
+
gridSpan: { end: '2024-01-12' },
|
|
167
|
+
} );
|
|
168
|
+
|
|
169
|
+
expect( data ).toHaveLength( 2 );
|
|
170
|
+
expect( data[ 1 ].data.slice( 0, 5 ).every( cell => cell.placeholder === true ) ).toBe( true );
|
|
171
|
+
} );
|
|
172
|
+
|
|
173
|
+
test( 'warns once and falls back on a bound that names no day', () => {
|
|
174
|
+
const { data } = buildCalendarHeatmapData( afternoon, {
|
|
175
|
+
weekStartsOn: 1,
|
|
176
|
+
timeZone: 'UTC',
|
|
177
|
+
gridSpan: { start: '2023-12' },
|
|
178
|
+
} );
|
|
179
|
+
|
|
180
|
+
expect( data ).toHaveLength( 1 );
|
|
181
|
+
expect( console ).toHaveWarned();
|
|
182
|
+
} );
|
|
183
|
+
} );
|
|
184
|
+
|
|
185
|
+
describe( 'buildCalendarHeatmapData labels in a host locale', () => {
|
|
186
|
+
// Three whole December weeks, so the first month keeps its label.
|
|
187
|
+
const series: DataPointDate[] = [
|
|
188
|
+
{ dateString: '2023-12-11', value: 1 },
|
|
189
|
+
{ dateString: '2024-01-03', value: 5 },
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
test( 'writes row, month and cell labels in the locale', () => {
|
|
193
|
+
const { data, rowLabels } = buildCalendarHeatmapData( series, {
|
|
194
|
+
weekStartsOn: 1,
|
|
195
|
+
locale: 'de-DE',
|
|
196
|
+
} );
|
|
197
|
+
|
|
198
|
+
expect( rowLabels ).toEqual( [ 'Mo', '', 'Mi', '', 'Fr', '', '' ] );
|
|
199
|
+
expect( data.map( column => column.label ) ).toEqual( [ 'Dez', '', '', 'Jan' ] );
|
|
200
|
+
expect( data[ 3 ].data[ 2 ].label ).toBe( 'Mi., 3. Jan. 2024' );
|
|
201
|
+
} );
|
|
202
|
+
|
|
203
|
+
test( 'does not let the locale override an explicit weekStartsOn', () => {
|
|
204
|
+
// de-DE starts its week on Monday; the caller asked for Sunday.
|
|
205
|
+
const { rowLabels } = buildCalendarHeatmapData( series, {
|
|
206
|
+
weekStartsOn: 0,
|
|
207
|
+
locale: 'de-DE',
|
|
208
|
+
} );
|
|
209
|
+
|
|
210
|
+
expect( rowLabels ).toEqual( [ 'So', '', 'Di', '', 'Do', '', '' ] );
|
|
211
|
+
} );
|
|
212
|
+
|
|
213
|
+
test( 'repairs a WordPress locale and falls back rather than throwing on a bad one', () => {
|
|
214
|
+
expect(
|
|
215
|
+
buildCalendarHeatmapData( series, { weekStartsOn: 1, locale: 'de_DE' } ).rowLabels
|
|
216
|
+
).toEqual( [ 'Mo', '', 'Mi', '', 'Fr', '', '' ] );
|
|
217
|
+
|
|
218
|
+
expect( () =>
|
|
219
|
+
buildCalendarHeatmapData( series, {
|
|
220
|
+
weekStartsOn: 1,
|
|
221
|
+
locale: 'not a tag',
|
|
222
|
+
timeZone: 'Nowhere/Fictional',
|
|
223
|
+
} )
|
|
224
|
+
).not.toThrow();
|
|
225
|
+
expect( console ).toHaveWarned();
|
|
226
|
+
} );
|
|
227
|
+
} );
|
|
228
|
+
|
|
229
|
+
describe( 'buildCalendarHeatmapData across a DST transition', () => {
|
|
230
|
+
// Chile moves its clocks on 2024-09-08, at midnight rather than in the small
|
|
231
|
+
// hours, which is where local-midnight day arithmetic loses a day.
|
|
232
|
+
const daily: DataPointDate[] = Array.from( { length: 14 }, ( _, index ) => ( {
|
|
233
|
+
date: new Date( Date.UTC( 2024, 8, 2 + index, 16 ) ),
|
|
234
|
+
value: index,
|
|
235
|
+
} ) );
|
|
236
|
+
|
|
237
|
+
test( 'draws every day exactly once', () => {
|
|
238
|
+
const { data } = buildCalendarHeatmapData( daily, {
|
|
239
|
+
weekStartsOn: 1,
|
|
240
|
+
timeZone: 'America/Santiago',
|
|
241
|
+
} );
|
|
242
|
+
|
|
243
|
+
expect( data ).toHaveLength( 2 );
|
|
244
|
+
expect( labelsCarryingValues( data ) ).toHaveLength( 14 );
|
|
245
|
+
expect( data[ 0 ].data.map( cell => cell.value ) ).toEqual( [ 0, 1, 2, 3, 4, 5, 6 ] );
|
|
246
|
+
expect( data[ 1 ].data.map( cell => cell.value ) ).toEqual( [ 7, 8, 9, 10, 11, 12, 13 ] );
|
|
247
|
+
} );
|
|
248
|
+
} );
|
|
@@ -28,6 +28,12 @@ export type HeatmapColumn = {
|
|
|
28
28
|
/** x-axis label for this column. Empty/omitted renders blank. */
|
|
29
29
|
label?: string;
|
|
30
30
|
data: HeatmapCell[];
|
|
31
|
+
/**
|
|
32
|
+
* A per-row roll-up such as a total or an average. Left out of the color
|
|
33
|
+
* scale, drawn unfilled with its figure always printed, on an `auto` track
|
|
34
|
+
* one gap apart from the data beside it. The chart never computes it.
|
|
35
|
+
*/
|
|
36
|
+
summary?: boolean;
|
|
31
37
|
};
|
|
32
38
|
|
|
33
39
|
export type HeatmapTooltipData = {
|
|
@@ -69,8 +75,8 @@ export interface HeatmapChartProps
|
|
|
69
75
|
/** Floor a cell's height (px) in non-compact mode; see `minCellWidth`. */
|
|
70
76
|
minCellHeight?: number;
|
|
71
77
|
/**
|
|
72
|
-
* Color the cell scale interpolates toward at the highest value
|
|
73
|
-
*
|
|
78
|
+
* Color the cell scale interpolates toward at the highest value. Defaults to the
|
|
79
|
+
* first series palette slot, `--a8c-charts-color-series-1`.
|
|
74
80
|
*/
|
|
75
81
|
primaryColor?: string;
|
|
76
82
|
renderTooltip?: ( data: HeatmapTooltipData ) => ReactNode;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useChartFormatting } from '../../providers';
|
|
3
|
+
import { buildCalendarHeatmapData } from './private';
|
|
4
|
+
import type { CalendarHeatmapOptions, CalendarHeatmapResult } from './private';
|
|
5
|
+
import type { DataPointDate } from '../../types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `buildCalendarHeatmapData` with the locale and zone taken from
|
|
9
|
+
* `GlobalChartsProvider` where the caller names neither.
|
|
10
|
+
*
|
|
11
|
+
* @param series - Points to bucket. Held by reference, so a caller that rebuilds it each render defeats the memo.
|
|
12
|
+
* @param options - As for `buildCalendarHeatmapData`; `locale` and `timeZone` win over the provider.
|
|
13
|
+
* @return Columns and row labels for `HeatmapChart`.
|
|
14
|
+
*/
|
|
15
|
+
export const useCalendarHeatmapData = (
|
|
16
|
+
series: DataPointDate[],
|
|
17
|
+
options: CalendarHeatmapOptions = {}
|
|
18
|
+
): CalendarHeatmapResult => {
|
|
19
|
+
const formatting = useChartFormatting();
|
|
20
|
+
|
|
21
|
+
// `??` rather than a spread, so an explicit `undefined` in `options` reads as
|
|
22
|
+
// "unset" and still picks the provider up.
|
|
23
|
+
const locale = options.locale ?? formatting.locale;
|
|
24
|
+
const timeZone = options.timeZone ?? formatting.timeZone;
|
|
25
|
+
const { weekStartsOn, hideOutOfRangeDays } = options;
|
|
26
|
+
const gridStart = options.gridSpan?.start;
|
|
27
|
+
const gridEnd = options.gridSpan?.end;
|
|
28
|
+
|
|
29
|
+
// Options are spread into primitives: the object itself is rebuilt on each render,
|
|
30
|
+
// even by a caller that memoized its own, and would defeat this memo.
|
|
31
|
+
return useMemo(
|
|
32
|
+
() =>
|
|
33
|
+
buildCalendarHeatmapData( series, {
|
|
34
|
+
locale,
|
|
35
|
+
timeZone,
|
|
36
|
+
weekStartsOn,
|
|
37
|
+
hideOutOfRangeDays,
|
|
38
|
+
gridSpan: { start: gridStart, end: gridEnd },
|
|
39
|
+
} ),
|
|
40
|
+
[ series, locale, timeZone, weekStartsOn, hideOutOfRangeDays, gridStart, gridEnd ]
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -13,8 +13,8 @@ export const SUBPIXEL_TOLERANCE = 0.5;
|
|
|
13
13
|
* Counts how many leading rows fit inside the content container.
|
|
14
14
|
*
|
|
15
15
|
* Rows are read from the DOM rather than derived from constants: row height
|
|
16
|
-
* depends on
|
|
17
|
-
* as a label, so any restated number would drift.
|
|
16
|
+
* depends on `--a8c-charts-dimension-leaderboard-row-gap`, label wrapping, and
|
|
17
|
+
* whatever a caller renders as a label, so any restated number would drift.
|
|
18
18
|
*
|
|
19
19
|
* Every row is a direct grid child marked with `data-row-index`; interactive rows
|
|
20
20
|
* use a button wrapper and non-interactive rows use a div wrapper.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __ } from '@wordpress/i18n';
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { useGlobalChartsContext } from '../../../providers';
|
|
4
4
|
import type { BaseLegendItem } from '../../../components/legend';
|
|
5
5
|
import type { LeaderboardEntry } from '../../../types';
|
|
6
6
|
|
|
@@ -35,7 +35,6 @@ export function useLeaderboardLegendItems( {
|
|
|
35
35
|
comparison?: string;
|
|
36
36
|
};
|
|
37
37
|
} ): BaseLegendItem[] {
|
|
38
|
-
const { leaderboardChart: leaderboardChartSettings } = useGlobalChartsTheme();
|
|
39
38
|
const { getElementStyles } = useGlobalChartsContext();
|
|
40
39
|
|
|
41
40
|
return useMemo( () => {
|
|
@@ -48,7 +47,7 @@ export function useLeaderboardLegendItems( {
|
|
|
48
47
|
// Add current period legend item
|
|
49
48
|
const { color: resolvedPrimaryColor } = getElementStyles( {
|
|
50
49
|
index: 0,
|
|
51
|
-
overrideColor: primaryColor
|
|
50
|
+
overrideColor: primaryColor,
|
|
52
51
|
} );
|
|
53
52
|
|
|
54
53
|
items.push( {
|
|
@@ -60,7 +59,7 @@ export function useLeaderboardLegendItems( {
|
|
|
60
59
|
if ( withComparison && ! withOverlayLabel ) {
|
|
61
60
|
const { color: resolvedSecondaryColor } = getElementStyles( {
|
|
62
61
|
index: 1,
|
|
63
|
-
overrideColor: secondaryColor
|
|
62
|
+
overrideColor: secondaryColor,
|
|
64
63
|
} );
|
|
65
64
|
|
|
66
65
|
items.push( {
|
|
@@ -76,7 +75,6 @@ export function useLeaderboardLegendItems( {
|
|
|
76
75
|
secondaryColor,
|
|
77
76
|
withComparison,
|
|
78
77
|
legendLabels,
|
|
79
|
-
leaderboardChartSettings,
|
|
80
78
|
getElementStyles,
|
|
81
79
|
withOverlayLabel,
|
|
82
80
|
] );
|