| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Place desriptions on a series and its points.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- 'use strict';
- import AnnotationsA11y from '../AnnotationsA11y.js';
- var getPointAnnotationTexts = AnnotationsA11y.getPointAnnotationTexts;
- import ChartUtilities from '../../Utils/ChartUtilities.js';
- var getAxisDescription = ChartUtilities.getAxisDescription, getSeriesFirstPointElement = ChartUtilities.getSeriesFirstPointElement, getSeriesA11yElement = ChartUtilities.getSeriesA11yElement, unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
- import HTMLUtilities from '../../Utils/HTMLUtilities.js';
- var reverseChildNodes = HTMLUtilities.reverseChildNodes, stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString;
- import Tooltip from '../../../Core/Tooltip.js';
- import U from '../../../Core/Utilities.js';
- var find = U.find, format = U.format, isNumber = U.isNumber, numberFormat = U.numberFormat, pick = U.pick, defined = U.defined;
- /* eslint-disable valid-jsdoc */
- /**
- * @private
- */
- function findFirstPointWithGraphic(point) {
- var sourcePointIndex = point.index;
- if (!point.series || !point.series.data || !defined(sourcePointIndex)) {
- return null;
- }
- return find(point.series.data, function (p) {
- return !!(p &&
- typeof p.index !== 'undefined' &&
- p.index > sourcePointIndex &&
- p.graphic &&
- p.graphic.element);
- }) || null;
- }
- /**
- * @private
- */
- function shouldAddDummyPoint(point) {
- // Note: Sunburst series use isNull for hidden points on drilldown.
- // Ignore these.
- var isSunburst = point.series && point.series.is('sunburst'), isNull = point.isNull;
- return isNull && !isSunburst;
- }
- /**
- * @private
- */
- function makeDummyElement(point, pos) {
- var renderer = point.series.chart.renderer, dummy = renderer.rect(pos.x, pos.y, 1, 1);
- dummy.attr({
- 'class': 'highcharts-a11y-dummy-point',
- fill: 'none',
- opacity: 0,
- 'fill-opacity': 0,
- 'stroke-opacity': 0
- });
- return dummy;
- }
- /**
- * @private
- * @param {Highcharts.Point} point
- * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|undefined}
- */
- function addDummyPointElement(point) {
- var series = point.series, firstPointWithGraphic = findFirstPointWithGraphic(point), firstGraphic = firstPointWithGraphic && firstPointWithGraphic.graphic, parentGroup = firstGraphic ?
- firstGraphic.parentGroup :
- series.graph || series.group, dummyPos = firstPointWithGraphic ? {
- x: pick(point.plotX, firstPointWithGraphic.plotX, 0),
- y: pick(point.plotY, firstPointWithGraphic.plotY, 0)
- } : {
- x: pick(point.plotX, 0),
- y: pick(point.plotY, 0)
- }, dummyElement = makeDummyElement(point, dummyPos);
- if (parentGroup && parentGroup.element) {
- point.graphic = dummyElement;
- point.hasDummyGraphic = true;
- dummyElement.add(parentGroup);
- // Move to correct pos in DOM
- parentGroup.element.insertBefore(dummyElement.element, firstGraphic ? firstGraphic.element : null);
- return dummyElement.element;
- }
- }
- /**
- * @private
- * @param {Highcharts.Series} series
- * @return {boolean}
- */
- function hasMorePointsThanDescriptionThreshold(series) {
- var chartA11yOptions = series.chart.options.accessibility, threshold = (chartA11yOptions.series.pointDescriptionEnabledThreshold);
- return !!(threshold !== false &&
- series.points &&
- series.points.length >= threshold);
- }
- /**
- * @private
- * @param {Highcharts.Series} series
- * @return {boolean}
- */
- function shouldSetScreenReaderPropsOnPoints(series) {
- var seriesA11yOptions = series.options.accessibility || {};
- return !hasMorePointsThanDescriptionThreshold(series) &&
- !seriesA11yOptions.exposeAsGroupOnly;
- }
- /**
- * @private
- * @param {Highcharts.Series} series
- * @return {boolean}
- */
- function shouldSetKeyboardNavPropsOnPoints(series) {
- var chartA11yOptions = series.chart.options.accessibility, seriesNavOptions = chartA11yOptions.keyboardNavigation.seriesNavigation;
- return !!(series.points && (series.points.length <
- seriesNavOptions.pointNavigationEnabledThreshold ||
- seriesNavOptions.pointNavigationEnabledThreshold === false));
- }
- /**
- * @private
- * @param {Highcharts.Series} series
- * @return {boolean}
- */
- function shouldDescribeSeriesElement(series) {
- var chart = series.chart, chartOptions = chart.options.chart || {}, chartHas3d = chartOptions.options3d && chartOptions.options3d.enabled, hasMultipleSeries = chart.series.length > 1, describeSingleSeriesOption = chart.options.accessibility.series.describeSingleSeries, exposeAsGroupOnlyOption = (series.options.accessibility || {}).exposeAsGroupOnly, noDescribe3D = chartHas3d && hasMultipleSeries;
- return !noDescribe3D && (hasMultipleSeries || describeSingleSeriesOption ||
- exposeAsGroupOnlyOption || hasMorePointsThanDescriptionThreshold(series));
- }
- /**
- * @private
- * @param {Highcharts.Point} point
- * @param {number} value
- * @return {string}
- */
- function pointNumberToString(point, value) {
- var chart = point.series.chart, a11yPointOptions = chart.options.accessibility.point || {}, tooltipOptions = point.series.tooltipOptions || {}, lang = chart.options.lang;
- if (isNumber(value)) {
- return numberFormat(value, a11yPointOptions.valueDecimals ||
- tooltipOptions.valueDecimals ||
- -1, lang.decimalPoint, lang.accessibility.thousandsSep || lang.thousandsSep);
- }
- return value;
- }
- /**
- * @private
- * @param {Highcharts.Series} series
- * @return {string}
- */
- function getSeriesDescriptionText(series) {
- var seriesA11yOptions = series.options.accessibility || {}, descOpt = seriesA11yOptions.description;
- return descOpt && series.chart.langFormat('accessibility.series.description', {
- description: descOpt,
- series: series
- }) || '';
- }
- /**
- * @private
- * @param {Highcharts.series} series
- * @param {string} axisCollection
- * @return {string}
- */
- function getSeriesAxisDescriptionText(series, axisCollection) {
- var axis = series[axisCollection];
- return series.chart.langFormat('accessibility.series.' + axisCollection + 'Description', {
- name: getAxisDescription(axis),
- series: series
- });
- }
- /**
- * Get accessible time description for a point on a datetime axis.
- *
- * @private
- * @function Highcharts.Point#getTimeDescription
- * @param {Highcharts.Point} point
- * @return {string|undefined}
- * The description as string.
- */
- function getPointA11yTimeDescription(point) {
- var series = point.series, chart = series.chart, a11yOptions = chart.options.accessibility.point || {}, hasDateXAxis = series.xAxis && series.xAxis.dateTime;
- if (hasDateXAxis) {
- var tooltipDateFormat = Tooltip.prototype.getXDateFormat.call({
- getDateFormat: Tooltip.prototype.getDateFormat,
- chart: chart
- }, point, chart.options.tooltip, series.xAxis), dateFormat = a11yOptions.dateFormatter &&
- a11yOptions.dateFormatter(point) ||
- a11yOptions.dateFormat ||
- tooltipDateFormat;
- return chart.time.dateFormat(dateFormat, point.x, void 0);
- }
- }
- /**
- * @private
- * @param {Highcharts.Point} point
- * @return {string}
- */
- function getPointXDescription(point) {
- var timeDesc = getPointA11yTimeDescription(point), xAxis = point.series.xAxis || {}, pointCategory = xAxis.categories && defined(point.category) &&
- ('' + point.category).replace('<br/>', ' '), canUseId = point.id && point.id.indexOf('highcharts-') < 0, fallback = 'x, ' + point.x;
- return point.name || timeDesc || pointCategory ||
- (canUseId ? point.id : fallback);
- }
- /**
- * @private
- * @param {Highcharts.Point} point
- * @param {string} prefix
- * @param {string} suffix
- * @return {string}
- */
- function getPointArrayMapValueDescription(point, prefix, suffix) {
- var pre = prefix || '', suf = suffix || '', keyToValStr = function (key) {
- var num = pointNumberToString(point, pick(point[key], point.options[key]));
- return key + ': ' + pre + num + suf;
- }, pointArrayMap = point.series.pointArrayMap;
- return pointArrayMap.reduce(function (desc, key) {
- return desc + (desc.length ? ', ' : '') + keyToValStr(key);
- }, '');
- }
- /**
- * @private
- * @param {Highcharts.Point} point
- * @return {string}
- */
- function getPointValue(point) {
- var series = point.series, a11yPointOpts = series.chart.options.accessibility.point || {}, tooltipOptions = series.tooltipOptions || {}, valuePrefix = a11yPointOpts.valuePrefix ||
- tooltipOptions.valuePrefix || '', valueSuffix = a11yPointOpts.valueSuffix ||
- tooltipOptions.valueSuffix || '', fallbackKey = (typeof point.value !==
- 'undefined' ?
- 'value' : 'y'), fallbackDesc = pointNumberToString(point, point[fallbackKey]);
- if (point.isNull) {
- return series.chart.langFormat('accessibility.series.nullPointValue', {
- point: point
- });
- }
- if (series.pointArrayMap) {
- return getPointArrayMapValueDescription(point, valuePrefix, valueSuffix);
- }
- return valuePrefix + fallbackDesc + valueSuffix;
- }
- /**
- * Return the description for the annotation(s) connected to a point, or empty
- * string if none.
- *
- * @private
- * @param {Highcharts.Point} point The data point to get the annotation info from.
- * @return {string} Annotation description
- */
- function getPointAnnotationDescription(point) {
- var chart = point.series.chart;
- var langKey = 'accessibility.series.pointAnnotationsDescription';
- var annotations = getPointAnnotationTexts(point);
- var context = { point: point, annotations: annotations };
- return annotations.length ? chart.langFormat(langKey, context) : '';
- }
- /**
- * Return string with information about point.
- * @private
- * @return {string}
- */
- function getPointValueDescription(point) {
- var series = point.series, chart = series.chart, pointValueDescriptionFormat = chart.options.accessibility
- .point.valueDescriptionFormat, showXDescription = pick(series.xAxis &&
- series.xAxis.options.accessibility &&
- series.xAxis.options.accessibility.enabled, !chart.angular), xDesc = showXDescription ? getPointXDescription(point) : '', context = {
- point: point,
- index: defined(point.index) ? (point.index + 1) : '',
- xDescription: xDesc,
- value: getPointValue(point),
- separator: showXDescription ? ', ' : ''
- };
- return format(pointValueDescriptionFormat, context, chart);
- }
- /**
- * Return string with information about point.
- * @private
- * @return {string}
- */
- function defaultPointDescriptionFormatter(point) {
- var series = point.series, chart = series.chart, valText = getPointValueDescription(point), description = point.options && point.options.accessibility &&
- point.options.accessibility.description, userDescText = description ? ' ' + description : '', seriesNameText = chart.series.length > 1 && series.name ?
- ' ' + series.name + '.' : '', annotationsDesc = getPointAnnotationDescription(point), pointAnnotationsText = annotationsDesc ? ' ' + annotationsDesc : '';
- point.accessibility = point.accessibility || {};
- point.accessibility.valueDescription = valText;
- return valText + userDescText + seriesNameText + pointAnnotationsText;
- }
- /**
- * Set a11y props on a point element
- * @private
- * @param {Highcharts.Point} point
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} pointElement
- */
- function setPointScreenReaderAttribs(point, pointElement) {
- var series = point.series, a11yPointOptions = series.chart.options.accessibility.point || {}, seriesA11yOptions = series.options.accessibility || {}, label = stripHTMLTags(seriesA11yOptions.pointDescriptionFormatter &&
- seriesA11yOptions.pointDescriptionFormatter(point) ||
- a11yPointOptions.descriptionFormatter &&
- a11yPointOptions.descriptionFormatter(point) ||
- defaultPointDescriptionFormatter(point));
- pointElement.setAttribute('role', 'img');
- pointElement.setAttribute('aria-label', label);
- }
- /**
- * Add accessible info to individual point elements of a series
- * @private
- * @param {Highcharts.Series} series
- */
- function describePointsInSeries(series) {
- var setScreenReaderProps = shouldSetScreenReaderPropsOnPoints(series), setKeyboardProps = shouldSetKeyboardNavPropsOnPoints(series);
- if (setScreenReaderProps || setKeyboardProps) {
- series.points.forEach(function (point) {
- var _a, _b;
- var pointEl = point.graphic && point.graphic.element ||
- shouldAddDummyPoint(point) && addDummyPointElement(point);
- var pointA11yDisabled = ((_b = (_a = point.options) === null || _a === void 0 ? void 0 : _a.accessibility) === null || _b === void 0 ? void 0 : _b.enabled) === false;
- if (pointEl) {
- // We always set tabindex, as long as we are setting props.
- // When setting tabindex, also remove default outline to
- // avoid ugly border on click.
- pointEl.setAttribute('tabindex', '-1');
- pointEl.style.outline = '0';
- if (setScreenReaderProps && !pointA11yDisabled) {
- setPointScreenReaderAttribs(point, pointEl);
- }
- else {
- pointEl.setAttribute('aria-hidden', true);
- }
- }
- });
- }
- }
- /**
- * Return string with information about series.
- * @private
- * @return {string}
- */
- function defaultSeriesDescriptionFormatter(series) {
- var chart = series.chart, chartTypes = chart.types || [], description = getSeriesDescriptionText(series), shouldDescribeAxis = function (coll) {
- return chart[coll] && chart[coll].length > 1 && series[coll];
- }, xAxisInfo = getSeriesAxisDescriptionText(series, 'xAxis'), yAxisInfo = getSeriesAxisDescriptionText(series, 'yAxis'), summaryContext = {
- name: series.name || '',
- ix: series.index + 1,
- numSeries: chart.series && chart.series.length,
- numPoints: series.points && series.points.length,
- series: series
- }, combinationSuffix = chartTypes.length > 1 ? 'Combination' : '', summary = chart.langFormat('accessibility.series.summary.' + series.type + combinationSuffix, summaryContext) || chart.langFormat('accessibility.series.summary.default' + combinationSuffix, summaryContext);
- return summary + (description ? ' ' + description : '') + (shouldDescribeAxis('yAxis') ? ' ' + yAxisInfo : '') + (shouldDescribeAxis('xAxis') ? ' ' + xAxisInfo : '');
- }
- /**
- * Set a11y props on a series element
- * @private
- * @param {Highcharts.Series} series
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} seriesElement
- */
- function describeSeriesElement(series, seriesElement) {
- var seriesA11yOptions = series.options.accessibility || {}, a11yOptions = series.chart.options.accessibility, landmarkVerbosity = a11yOptions.landmarkVerbosity;
- // Handle role attribute
- if (seriesA11yOptions.exposeAsGroupOnly) {
- seriesElement.setAttribute('role', 'img');
- }
- else if (landmarkVerbosity === 'all') {
- seriesElement.setAttribute('role', 'region');
- } /* else do not add role */
- seriesElement.setAttribute('tabindex', '-1');
- seriesElement.style.outline = '0'; // Don't show browser outline on click, despite tabindex
- seriesElement.setAttribute('aria-label', stripHTMLTags(a11yOptions.series.descriptionFormatter &&
- a11yOptions.series.descriptionFormatter(series) ||
- defaultSeriesDescriptionFormatter(series)));
- }
- /**
- * Put accessible info on series and points of a series.
- * @param {Highcharts.Series} series The series to add info on.
- */
- function describeSeries(series) {
- var chart = series.chart, firstPointEl = getSeriesFirstPointElement(series), seriesEl = getSeriesA11yElement(series), is3d = chart.is3d && chart.is3d();
- if (seriesEl) {
- // For some series types the order of elements do not match the
- // order of points in series. In that case we have to reverse them
- // in order for AT to read them out in an understandable order.
- // Due to z-index issues we can not do this for 3D charts.
- if (seriesEl.lastChild === firstPointEl && !is3d) {
- reverseChildNodes(seriesEl);
- }
- describePointsInSeries(series);
- unhideChartElementFromAT(chart, seriesEl);
- if (shouldDescribeSeriesElement(series)) {
- describeSeriesElement(series, seriesEl);
- }
- else {
- seriesEl.setAttribute('aria-label', '');
- }
- }
- }
- var SeriesDescriber = {
- describeSeries: describeSeries,
- defaultPointDescriptionFormatter: defaultPointDescriptionFormatter,
- defaultSeriesDescriptionFormatter: defaultSeriesDescriptionFormatter,
- getPointA11yTimeDescription: getPointA11yTimeDescription,
- getPointXDescription: getPointXDescription,
- getPointValue: getPointValue,
- getPointValueDescription: getPointValueDescription
- };
- export default SeriesDescriber;
|