Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Name each widget chart after its widget, so screen readers can tell the charts on a dashboard apart.
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ function MyWidget( { chartData } ) {
| `dataFormat` | `DataFormat` | Yes | Format for values (tooltips): currency, number, percentage |
| `styles` | `BarChartStyle[]` | No | Styles for each series (by index) |
| `className` | `string` | No | CSS class for the chart container |
| `chartTitle` | `string` | No | What the chart shows, such as the widget title; the chart is named "<title> chart" |

## BarChartStyle Type

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* External dependencies
*/
import { render } from '@testing-library/react';
/**
* Internal dependencies
*/
import { mockBarChartSpy, resetMockCharts } from '../../../../../../tests/js/chart-test-utils';
import { BarChart } from '../bar-chart';

jest.mock( '@jetpack-premium-analytics/externals', () =>
jest.requireActual( '../../../../../../tests/js/chart-test-utils' ).mockChartExternals()
);

jest.mock(
'@wordpress/compose',
() => jest.requireActual( '../../../../../../tests/js/chart-test-utils' ).mockWordPressCompose
);

describe( 'BarChart', () => {
beforeEach( () => {
resetMockCharts();
} );

it.each( [
[ 'Sales by device', 'Sales by device chart' ],
[ undefined, undefined ],
] )( 'names the chart from chartTitle %s', ( chartTitle, ariaLabel ) => {
render(
<BarChart
chartData={ [ { label: 'Sales', data: [ { label: 'Mobile', value: 3 } ] } ] }
dataFormat={ { type: 'number' } }
chartTitle={ chartTitle }
/>
);

expect( mockBarChartSpy ).toHaveBeenLastCalledWith( expect.objectContaining( { ariaLabel } ) );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useCallback, useMemo, useId } from 'react';
* Internal dependencies
*/
import { RESIZE_DEBOUNCE_MS } from '../../constants';
import { isEmptyChartData, getEmptyChartDomain } from '../../helpers';
import { isEmptyChartData, formatChartName, getEmptyChartDomain } from '../../helpers';
import { ChartEmptyState } from '../chart-empty-state';
import { ChartTooltip } from '../chart-tooltip';
import styles from './bar-chart.module.scss';
Expand Down Expand Up @@ -48,6 +48,9 @@ export type BarChartProps = {

className?: string;

/** What the chart shows, such as the widget title; the chart is named "<title> chart". */
chartTitle?: string;

emptyStateIcon?: React.ComponentProps< typeof Icon >[ 'icon' ];

emptyStateText?: string;
Expand Down Expand Up @@ -116,6 +119,7 @@ export function BarChart( {
dataFormat,
styles: stylesProp,
className,
chartTitle,
emptyStateIcon,
emptyStateText,
showZeroValues = true,
Expand Down Expand Up @@ -192,6 +196,7 @@ export function BarChart( {
return (
<BarChartBase
chartId={ chartId }
ariaLabel={ formatChartName( chartTitle ) }
data={ styledChartData }
className={ clsx( styles.chart, className ) }
resizeDebounceTime={ RESIZE_DEBOUNCE_MS }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ second current-period series keeps its own dates.
| `compactWhenShort` | `boolean` | No | Degrade to a sparkline under 140px of chart area |
| `maxWidth` | `number` | No | Maximum chart width |
| `className` | `string` | No | CSS class for the chart container |
| `chartTitle` | `string` | No | What the chart shows, such as the widget title; the chart is named "<title> chart" |
| `chartId` | `string` | No | Identity the charts provider keys visibility on; generated when omitted. Change it whenever `defaultHiddenSeries` should be applied again |
| `defaultHiddenSeries` | `readonly string[]` | No | Labels of series hidden until revealed from the legend. Applied once per `chartId`, so only useful with `legendInteractive` |
| `legendInteractive` | `boolean` | No | Let the reader click legend items to show and hide series; the first item stays locked. Defaults to `false` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,20 @@ describe( 'ComparativeBarChart', () => {
setSettings( originalSettings );
} );

it( 'names the chart from chartTitle', () => {
render(
<ComparativeBarChart
series={ SERIES }
dataFormat={ DATA_FORMAT }
chartTitle="Traffic summary"
/>
);

expect( mockBarChartSpy ).toHaveBeenLastCalledWith(
expect.objectContaining( { ariaLabel: 'Traffic summary chart' } )
);
} );

it( 'passes no x tickFormat when no tick format is requested', () => {
render( <ComparativeBarChart series={ SERIES } dataFormat={ DATA_FORMAT } /> );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ import { useCallback, useId, useMemo, useState } from 'react';
* Internal dependencies
*/
import { RESIZE_DEBOUNCE_MS } from '../../constants';
import { isEmptyChartData, getFixedYAxis, formatBucketTooltipDate } from '../../helpers';
import {
isEmptyChartData,
formatChartName,
getFixedYAxis,
formatBucketTooltipDate,
} from '../../helpers';
import { resolvePrimarySeriesByGroup } from '../../helpers/resolve-series-names';
import { useLockedPrimaryLegendItems } from '../../hooks/use-locked-primary-legend-items';
import { alignSeriesDates } from '../chart-comparative-line/utils';
Expand Down Expand Up @@ -55,6 +60,9 @@ export type ComparativeBarChartProps = {

className?: string;

/** What the chart shows, such as the widget title; the chart is named "<title> chart". */
chartTitle?: string;

/** Format for chart values: y-axis ticks and tooltips. */
dataFormat: DataFormat;

Expand Down Expand Up @@ -124,6 +132,7 @@ export function ComparativeBarChart( {
series,
className,
chartId: providedChartId,
chartTitle,
dataFormat,
tickFormat: xTickFormatType,
tickResolution,
Expand Down Expand Up @@ -314,6 +323,7 @@ export function ComparativeBarChart( {
<Stack ref={ measureRef } direction="column" className={ clsx( styles.chart, className ) }>
<BarChart
chartId={ chartId }
ariaLabel={ formatChartName( chartTitle ) }
className={ styles.chartContent }
data={ alignedSeries }
options={ chartOptions }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ function MyWidget( { series } ) {
| `dataFormat` | `DataFormat` | Yes | Format for values (Y-axis ticks and tooltips) |
| `tickFormat` | `DateFormatName` | No | Named X-axis date format; uses the chart default when omitted |
| `className` | `string` | No | CSS class for the chart container |
| `chartTitle` | `string` | No | What the chart shows, such as the widget title; the chart is named "<title> chart" |
| `chartId` | `string` | No | Identity the charts provider keys visibility on; generated when omitted. Change it whenever `defaultHiddenSeries` should be applied again |
| `defaultHiddenSeries` | `readonly string[]` | No | Labels of series hidden until revealed from the legend. Applied once per `chartId`, so only useful with `legendInteractive` |
| `legendInteractive` | `boolean` | No | Let the reader click legend items to show and hide series; the first item stays locked. Defaults to `false` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,20 @@ describe( 'ComparativeLineChart', () => {
setSettings( originalSettings );
} );

it( 'names the chart from chartTitle', () => {
render(
<ComparativeLineChart
series={ SERIES }
dataFormat={ DATA_FORMAT }
chartTitle="Traffic summary"
/>
);

expect( mockLineChartSpy ).toHaveBeenLastCalledWith(
expect.objectContaining( { ariaLabel: 'Traffic summary chart' } )
);
} );

// `useChartMargin` sizes the gutters itself; overriding them here clipped the edge dates.
it.each( [
[ 'by default', DATA_FORMAT ],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { type ComponentProps } from 'react';
import { RESIZE_DEBOUNCE_MS } from '../../constants';
import {
isEmptyChartData,
formatChartName,
getFixedYAxis,
getPaddedYAxis,
getPinnedYTicks,
Expand Down Expand Up @@ -102,6 +103,9 @@ export type ComparativeLineChartProps = {

className?: string;

/** What the chart shows, such as the widget title; the chart is named "<title> chart". */
chartTitle?: string;

dataFormat: DataFormat;

/** Named date format for the X-axis ticks. Uses the chart default when omitted. */
Expand Down Expand Up @@ -151,13 +155,15 @@ export type ComparativeLineChartProps = {
| 'resizeDebounceTime'
| 'withTooltips'
| 'renderTooltip'
| 'ariaLabel'
>;

export function ComparativeLineChart( {
series,
styles: stylesProp,
className,
chartId,
chartTitle,
dataFormat,
tickFormat: xTickFormatType,
tickResolution,
Expand Down Expand Up @@ -304,6 +310,7 @@ export function ComparativeLineChart( {
<Stack ref={ measureRef } direction="column" className={ clsx( styles.chart, className ) }>
<LineChart
chartId={ resolvedChartId }
ariaLabel={ formatChartName( chartTitle ) }
className={ styles.chartContent }
data={ styledSeries }
options={ chartOptions }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { _n } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { resetMockCharts } from '../../../../../../tests/js/chart-test-utils';
import { resetMockCharts, setMockChartWidth } from '../../../../../../tests/js/chart-test-utils';
import * as comparativeBarChart from '../../chart-comparative-bar/comparative-bar-chart';
import * as comparativeLineChart from '../../chart-comparative-line/comparative-line-chart';
import { MetricTabsChart } from '../metric-tabs-chart';
Expand Down Expand Up @@ -180,6 +180,33 @@ describe( 'MetricTabsChart', () => {
);
} );

it.each( [
[ 'single-metric', 'line', mockLineSpy, [ METRIC ] ],
[ 'single-metric', 'bar', mockBarSpy, [ METRIC ] ],
[ 'tabbed', 'line', mockLineSpy, [ METRIC, VISITORS ] ],
[ 'tabbed', 'bar', mockBarSpy, [ METRIC, VISITORS ] ],
[ 'dropdown', 'line', mockLineSpy, [ METRIC, VISITORS ] ],
[ 'dropdown', 'bar', mockBarSpy, [ METRIC, VISITORS ] ],
] as const )( 'passes chartTitle to the %s %s chart', ( layout, type, spy, metrics ) => {
if ( layout === 'dropdown' ) {
setMockChartWidth( 100 );
}

render(
<MetricTabsChart
metrics={ [ ...metrics ] }
dataFormat={ DATA_FORMAT }
chartType={ type }
chartTitle="Traffic summary"
/>
);

expect( screen.queryByRole( 'tablist' ) !== null ).toBe( layout === 'tabbed' );
expect( spy ).toHaveBeenLastCalledWith(
expect.objectContaining( { chartTitle: 'Traffic summary' } )
);
} );

it( 'keeps the previous period as a same-group comparison series in both chart types', () => {
render( <MetricTabsChart metrics={ [ METRIC ] } dataFormat={ DATA_FORMAT } /> );
render( <MetricTabsChart metrics={ [ METRIC ] } dataFormat={ DATA_FORMAT } chartType="bar" /> );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ export interface MetricTabsChartProps {
controls?: ReactNode;
/** Accessible label for the metric tab list. */
groupLabel?: string;
/** What the chart shows, such as the widget title; the chart is named "<title> chart". */
chartTitle?: string;
/**
* The series' bucket size, declared to the x-axis so tick formats follow the
* known granularity rather than being inferred from point spacing.
Expand Down Expand Up @@ -222,6 +224,7 @@ function MetricChart( {
dataFormat,
chartType,
chartId,
chartTitle,
tickResolution,
onDatumClick,
baseline,
Expand All @@ -235,6 +238,7 @@ function MetricChart( {
chartType: MetricTabsChartType;
baseline?: ChartBaseline;
chartId: string;
chartTitle?: string;
tickResolution?: TickResolution;
onDatumClick?: ( date: Date ) => void;
empty?: ReactNode;
Expand Down Expand Up @@ -364,6 +368,7 @@ function MetricChart( {
return chartType === 'bar' ? (
<ComparativeBarChart
chartId={ chartId }
chartTitle={ chartTitle }
series={ series }
dataFormat={ resolvedDataFormat }
defaultHiddenSeries={ defaultHiddenSeries }
Expand All @@ -376,6 +381,7 @@ function MetricChart( {
) : (
<ComparativeLineChart
chartId={ chartId }
chartTitle={ chartTitle }
series={ series }
styles={ seriesStyles }
dataFormat={ resolvedDataFormat }
Expand Down Expand Up @@ -462,6 +468,7 @@ export function MetricTabsChart( {
onMetricChange,
controls,
groupLabel = __( 'Select metric', 'jetpack-premium-analytics-pkg' ),
chartTitle,
tickResolution,
onDatumClick,
tooltipMetrics = 'active',
Expand Down Expand Up @@ -561,6 +568,7 @@ export function MetricTabsChart( {
dataFormat={ dataFormat }
chartType={ chartType }
chartId={ chartIdFor( activeMetric ) }
chartTitle={ chartTitle }
tickResolution={ tickResolution }
onDatumClick={ onDatumClick }
empty={ empty }
Expand Down Expand Up @@ -634,6 +642,7 @@ export function MetricTabsChart( {
dataFormat={ dataFormat }
chartType={ chartType }
chartId={ chartIdFor( activeMetric ) }
chartTitle={ chartTitle }
tickResolution={ tickResolution }
onDatumClick={ onDatumClick }
empty={ empty }
Expand Down Expand Up @@ -683,6 +692,7 @@ export function MetricTabsChart( {
dataFormat={ dataFormat }
chartType={ chartType }
chartId={ chartIdFor( metric ) }
chartTitle={ chartTitle }
tickResolution={ tickResolution }
onDatumClick={ onDatumClick }
empty={ empty }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';

/**
* Name a chart after what it shows, so a screen reader can tell it from the widget around it.
*
* @param title - What the chart shows, such as the widget title.
* @return The chart's accessible name, or undefined to keep the chart's default.
*/
export function formatChartName( title?: string ): string | undefined {
if ( ! title ) {
return undefined;
}

return sprintf(
/* translators: %s is what a chart shows, e.g. "Sales by device". */
__( '%s chart', 'jetpack-premium-analytics-pkg' ),
title
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,6 @@ export {
export { monthlyHeatmapLifeStart } from './monthly-heatmap-life-start';
export { bucketRange, monthRange, yearRange, type PeriodBounds } from './period-range';
export { siteChartFormatting } from './site-chart-formatting';
export { formatChartName } from './format-chart-name';
export { formatComparisonSeriesLabel } from './format-comparison-series-label';
export { formatBucketTooltipDate } from './format-bucket-tooltip-date';
Loading
Loading