Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
*/

import { getGoalChartDataSelector, getGoalChartLabelsSelector } from './get_goal_chart_data';
import type { ChartSpecificScreenReaderData, ScreenReaderItem } from '../../../../state/chart_selectors';
import type { GlobalChartState } from '../../../../state/chart_state';
import { createCustomCachedSelector } from '../../../../state/create_selector';
import { getA11ySettingsSelector } from '../../../../state/selectors/get_accessibility_config';
import { getInternalChartStateSelector } from '../../../../state/selectors/get_internal_chart_state';
import { EMPTY_SCREEN_READER_ITEMS, type ScreenReaderItem } from '../../../../state/selectors/get_screenreader_data';

/** @internal */
export const getScreenReaderDataSelector = createCustomCachedSelector(
Expand All @@ -22,7 +22,7 @@ export const getScreenReaderDataSelector = createCustomCachedSelector(
getA11ySettingsSelector,
(state: GlobalChartState) => state,
],
(goalChartData, goalChartLabels, internalChartState, a11ySettings, state): ChartSpecificScreenReaderData => {
(goalChartData, goalChartLabels, internalChartState, a11ySettings, state): ScreenReaderItem[] => {
const screenReaderItems: ScreenReaderItem[] = [];

// Add chart type description first
Expand Down Expand Up @@ -69,6 +69,6 @@ export const getScreenReaderDataSelector = createCustomCachedSelector(
);
}

return { screenReaderItems };
return screenReaderItems.length > 0 ? screenReaderItems : EMPTY_SCREEN_READER_ITEMS;
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,10 @@ import { getGoalSpecSelector } from './get_goal_spec';
import { getPickedShapes } from './picked_shapes';
import { Colors } from '../../../../common/colors';
import type { TooltipInfo } from '../../../../components/tooltip/types';
import { EMPTY_TOOLTIP } from '../../../../state/chart_selectors';
import { createCustomCachedSelector } from '../../../../state/create_selector';
import type { BandViewModel } from '../../layout/types/viewmodel_types';

const EMPTY_TOOLTIP = Object.freeze({
header: null,
values: [],
});

const getBandColor = (value: number, bands: BandViewModel[]) =>
bands.find(({ value: v }) => {
return v >= value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ import { getPickedShapes } from './picked_shapes';
import { RGBATupleToString } from '../../../../common/color_library_wrappers';
import { Colors } from '../../../../common/colors';
import type { TooltipInfo } from '../../../../components/tooltip/types';
import { EMPTY_TOOLTIP } from '../../../../state/chart_selectors';
import { createCustomCachedSelector } from '../../../../state/create_selector';

const EMPTY_TOOLTIP = Object.freeze({
header: null,
values: [],
disableActions: false,
});

/** @internal */
export const getTooltipInfoSelector = createCustomCachedSelector(
[getHeatmapSpecSelector, getPickedShapes],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ import { partitionMultiGeometries } from './geometries';
import { getPartitionSpec } from './partition_spec';
import { getPickedShapes } from './picked_shapes';
import type { TooltipInfo } from '../../../../components/tooltip/types';
import { EMPTY_TOOLTIP } from '../../../../state/chart_selectors';
import { createCustomCachedSelector } from '../../../../state/create_selector';
import { pickShapesTooltipValues } from '../../layout/viewmodel/picked_shapes';

const EMPTY_TOOLTIP = Object.freeze({ header: null, values: [] });

/** @internal */
export const getTooltipInfoSelector = createCustomCachedSelector(
[getPartitionSpec, getPickedShapes, partitionMultiGeometries],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import { EMPTY_LEGEND_ITEM_EXTRA_VALUES } from '../../../../common/legend';
import type { SeriesKey } from '../../../../common/series_id';
import { ScaleType } from '../../../../scales/constants';
import { createCustomCachedSelector } from '../../../../state/create_selector';
import { getLegendConfigSelector } from '../../../../state/selectors/get_legend_config_selector';
import { getLegendItemExtraValues } from '../../tooltip/tooltip';

/** @internal */
export const getLegendItemExtraValuesSelector = createCustomCachedSelector(
[getTooltipInfoAndGeomsSelector, getComputedScalesSelector],
({ tooltip: { values } }, { xScale: { type } }): Map<SeriesKey, LegendItemExtraValues> =>
[getTooltipInfoAndGeomsSelector, getComputedScalesSelector, getLegendConfigSelector],
({ tooltip: { values } }, { xScale: { type } }, { legendValues }): Map<SeriesKey, LegendItemExtraValues> =>
// See https://github.com/elastic/elastic-charts/issues/2050
type === ScaleType.Ordinal ? EMPTY_LEGEND_ITEM_EXTRA_VALUES : getLegendItemExtraValues(values),
type === ScaleType.Ordinal || legendValues.length === 0
? EMPTY_LEGEND_ITEM_EXTRA_VALUES
: getLegendItemExtraValues(values),
);
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@

import React from 'react';

import type { ScreenReaderItem } from '../../state/chart_selectors';
import type { A11ySettings } from '../../state/selectors/get_accessibility_config';
import type { ScreenReaderItem } from '../../state/selectors/get_screenreader_data';

interface ScreenReaderItemsProps {
screenReaderItems?: ScreenReaderItem[];
screenReaderItems: ScreenReaderItem[];
}

/** @internal */
export function ScreenReaderItems({ screenReaderItems }: A11ySettings & ScreenReaderItemsProps) {
const hasScreenReaderItems = screenReaderItems && screenReaderItems.length > 0;
const hasScreenReaderItems = screenReaderItems.length > 0;

if (!hasScreenReaderItems) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ import type { GlobalChartState } from '../../state/chart_state';
import type { ScreenReaderSummaryData } from '../../state/selectors/get_screen_reader_summary';
import { getScreenReaderSummarySelector } from '../../state/selectors/get_screen_reader_summary';

const ScreenReaderSummaryComponent = ({ a11ySettings, screenReaderData }: ScreenReaderSummaryData) => {
const ScreenReaderSummaryComponent = ({ a11ySettings, screenReaderItems }: ScreenReaderSummaryData) => {
return (
<figcaption
className="echScreenReaderOnly"
id={`${a11ySettings.descriptionId}-summary`}
data-testid="echScreenReaderSummary"
>
<ScreenReaderDescription {...a11ySettings} />
<ScreenReaderItems {...a11ySettings} screenReaderItems={screenReaderData?.screenReaderItems} />
<ScreenReaderItems {...a11ySettings} screenReaderItems={screenReaderItems} />
</figcaption>
);
};
Expand Down
66 changes: 22 additions & 44 deletions packages/charts/src/state/chart_selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
import type { CSSProperties } from 'react';

import type { GlobalChartState } from './chart_state';
import { getA11ySettingsSelector } from './selectors/get_accessibility_config';
import { InitStatus } from './selectors/get_internal_is_intialized';
import type { ScreenReaderItem } from './selectors/get_screenreader_data';
import { getScreenReaderDataSelector } from './selectors/get_screenreader_data';
import type { TooltipVisibility } from './tooltip_visibility';
import type { DebugState } from './types';
import { DEFAULT_CSS_CURSOR } from '../common/constants';
Expand All @@ -22,31 +23,12 @@ import type { AnchorPosition } from '../components/portal/types';
import type { TooltipInfo } from '../components/tooltip/types';
import type { Dimensions } from '../utils/dimensions';

/** @internal */
export interface ScreenReaderItem {
/** The label for this part of the summary */
label: string;
/** Optional ID for referencing this part */
id?: string;
/** The value for this part of the summary */
value: string;
}

/** @internal */
export interface ChartSpecificScreenReaderData {
/** Custom summary parts to include in the consolidated summary */
screenReaderItems?: ScreenReaderItem[];
}

/** @internal */
export interface LegendItemLabel {
label: string;
depth: number;
}

/** @internal */
export const EMPTY_LEGEND_ITEM_LIST: LegendItemLabel[] = [];

/**
* A set of chart-type-dependant functions that are required by all chart types
* @internal
Expand Down Expand Up @@ -157,7 +139,7 @@ export interface ChartSelectors {
/**
* Get chart-specific data for screen reader accessibility
*/
getScreenReaderData?(globalState: GlobalChartState): ChartSpecificScreenReaderData;
getScreenReaderData(globalState: GlobalChartState): ScreenReaderItem[];

/**
* Get the domain of the vertical and horizontal small multiple grids
Expand All @@ -170,10 +152,20 @@ export interface ChartSelectors {
canDisplayChartTitles(globalState: GlobalChartState): boolean;
}

/** @internal */
export type ChartSelectorsFactory = () => ChartSelectors;
type ChartSelectorsFactory = () => ChartSelectors;

const EMPTY_TOOLTIP = Object.freeze({ header: null, values: [] });
const EMPTY_LEGEND_ITEM_LIST: LegendItemLabel[] = [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const EMPTY_LEGEND_ITEM_LIST: LegendItemLabel[] = [];
const EMPTY_LEGEND_ITEM_LIST: LegendItemLabel[] = Object.freeze([]);

Do you think it's too much to use Object.freeze on all these? It's unlikely they would change based on our aversion to mutations but still you never know. I don't think freeze affects runtime performance in any meaningful way.

Up to you...

/** @internal */
export const EMPTY_TOOLTIP = { header: null, values: [] };
const EMPTY_DIMENSION = { top: 0, left: 0, width: 0, height: 0 };
const EMPTY_SM_DOMAINS: SmallMultiplesSeriesDomains = { smVDomain: [], smHDomain: [] };
const EMPTY_OBJ = {};
const EMPTY_TOOLTIP_VISIBILITY: TooltipVisibility = {
visible: false,
isExternal: false,
displayOnly: false,
isPinnable: false,
};

type CallbackCreator = () => (state: GlobalChartState) => void;

Expand All @@ -195,30 +187,16 @@ export const createChartSelectorsFactory =
getLegendItemsLabels: () => EMPTY_LEGEND_ITEM_LIST,
getLegendExtraValues: () => EMPTY_LEGEND_ITEM_EXTRA_VALUES,
getPointerCursor: () => DEFAULT_CSS_CURSOR,
isTooltipVisible: () => ({
visible: false,
isExternal: false,
displayOnly: false,
isPinnable: false,
}),
isTooltipVisible: () => EMPTY_TOOLTIP_VISIBILITY,
getTooltipInfo: () => EMPTY_TOOLTIP,
getTooltipAnchor: () => null,
getProjectionContainerArea: () => ({ top: 0, left: 0, width: 0, height: 0 }),
getMainProjectionArea: () => ({ top: 0, left: 0, width: 0, height: 0 }),
getProjectionContainerArea: () => EMPTY_DIMENSION,
getMainProjectionArea: () => EMPTY_DIMENSION,
getBrushArea: () => null,
getDebugState: () => ({}),
getDebugState: () => EMPTY_OBJ,
getChartTypeDescription: () => '',
// The default screen reader data returns just the chart type description.
getScreenReaderData: (state: GlobalChartState): ChartSpecificScreenReaderData => {
const a11ySettings = getA11ySettingsSelector(state);
const chartTypeDescription = chartSelectors.getChartTypeDescription(state);
return {
screenReaderItems: chartTypeDescription
? [{ label: 'Chart type', id: a11ySettings.defaultSummaryId, value: chartTypeDescription }]
: [],
};
},
getSmallMultiplesDomains: () => ({ smVDomain: [], smHDomain: [] }),
getScreenReaderData: getScreenReaderDataSelector,
getSmallMultiplesDomains: () => EMPTY_SM_DOMAINS,
canDisplayChartTitles: () => true,
...overrides,
eventCallbacks: (state: GlobalChartState) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { getInternalChartStateSelector } from './get_internal_chart_state';
import type { GlobalChartState } from '../chart_state';
import { createCustomCachedSelector } from '../create_selector';

/** @internal */
export const getInternalChartTypeDescSelector = createCustomCachedSelector(
[(globalChartState: GlobalChartState) => globalChartState, getInternalChartStateSelector],
(globalChartState, internalChartState): string => {
return internalChartState?.getChartTypeDescription(globalChartState) ?? '';
},
);
12 changes: 6 additions & 6 deletions packages/charts/src/state/selectors/get_screen_reader_summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ import type { A11ySettings } from './get_accessibility_config';
import { DEFAULT_A11Y_SETTINGS, getA11ySettingsSelector } from './get_accessibility_config';
import { getInternalChartStateSelector } from './get_internal_chart_state';
import { getInternalIsInitializedSelector, InitStatus } from './get_internal_is_intialized';
import type { ChartSpecificScreenReaderData } from '../chart_selectors';
import type { ScreenReaderItem } from './get_screenreader_data';
import type { GlobalChartState } from '../chart_state';
import { createCustomCachedSelector } from '../create_selector';

/** @internal */
export interface ScreenReaderSummaryData {
a11ySettings: A11ySettings;
screenReaderData?: ChartSpecificScreenReaderData;
screenReaderItems: ScreenReaderItem[];
}

const DEFAULT_SCREEN_READER_SUMMARY: ScreenReaderSummaryData = {
a11ySettings: DEFAULT_A11Y_SETTINGS,
screenReaderData: undefined,
screenReaderItems: [],
};

/** @internal */
Expand All @@ -38,12 +38,12 @@ export const getScreenReaderSummarySelector = createCustomCachedSelector(
return DEFAULT_SCREEN_READER_SUMMARY;
}

// Get chart-specific screen reader data
const screenReaderData = internalChartState.getScreenReaderData?.(state);
// Get chart-specific screen reader items
const screenReaderItems = internalChartState.getScreenReaderData(state);

return {
a11ySettings,
screenReaderData,
screenReaderItems,
};
},
);
34 changes: 34 additions & 0 deletions packages/charts/src/state/selectors/get_screenreader_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { getA11ySettingsSelector } from './get_accessibility_config';
import { getInternalChartTypeDescSelector } from './get_internal_chart_type_desc';
import { createCustomCachedSelector } from '../create_selector';

/** @internal */
export interface ScreenReaderItem {
/** The label for this part of the summary */
label: string;
/** Optional ID for referencing this part */
id?: string;
/** The value for this part of the summary */
value: string;
}

/** @internal */
export const EMPTY_SCREEN_READER_ITEMS: ScreenReaderItem[] = [];

/** @internal */
export const getScreenReaderDataSelector = createCustomCachedSelector(
[getA11ySettingsSelector, getInternalChartTypeDescSelector],
(a11ySettings, chartTypeDescription): ScreenReaderItem[] => {
return chartTypeDescription
? [{ label: 'Chart type', id: a11ySettings.defaultSummaryId, value: chartTypeDescription }]
: EMPTY_SCREEN_READER_ITEMS;
},
);