/* *
|
*
|
* (c) 2009-2019 Øystein Moseng
|
*
|
* Accessibility component for chart info region and table.
|
*
|
* License: www.highcharts.com/license
|
*
|
* */
|
|
'use strict';
|
|
Object.defineProperty(exports, "__esModule", {
|
value: true
|
});
|
|
var _Globals = require('../../../parts/Globals.js');
|
|
var _Globals2 = _interopRequireDefault(_Globals);
|
|
var _AccessibilityComponent = require('../AccessibilityComponent.js');
|
|
var _AccessibilityComponent2 = _interopRequireDefault(_AccessibilityComponent);
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
var merge = _Globals2.default.merge,
|
pick = _Globals2.default.pick;
|
|
/**
|
* Return simplified text description of chart type. Some types will not be
|
* familiar to most users, but in those cases we try to add a description of the
|
* type.
|
*
|
* @private
|
* @function Highcharts.Chart#getTypeDescription
|
* @param {Array<string>} types The series types in this chart.
|
* @return {string} The text description of the chart type.
|
*/
|
_Globals2.default.Chart.prototype.getTypeDescription = function (types) {
|
var firstType = types[0],
|
firstSeries = this.series && this.series[0] || {},
|
mapTitle = firstSeries.mapTitle,
|
formatContext = {
|
numSeries: this.series.length,
|
numPoints: firstSeries.points && firstSeries.points.length,
|
chart: this,
|
mapTitle: mapTitle
|
};
|
|
if (!firstType) {
|
return this.langFormat('accessibility.chartTypes.emptyChart', formatContext);
|
}
|
|
if (firstType === 'map') {
|
return mapTitle ? this.langFormat('accessibility.chartTypes.mapTypeDescription', formatContext) : this.langFormat('accessibility.chartTypes.unknownMap', formatContext);
|
}
|
|
if (this.types.length > 1) {
|
return this.langFormat('accessibility.chartTypes.combinationChart', formatContext);
|
}
|
|
var typeDesc = this.langFormat('accessibility.seriesTypeDescriptions.' + firstType, { chart: this }),
|
multi = this.series && this.series.length === 1 ? 'Single' : 'Multiple';
|
|
return (this.langFormat('accessibility.chartTypes.' + firstType + multi, formatContext) || this.langFormat('accessibility.chartTypes.default' + multi, formatContext)) + (typeDesc ? ' ' + typeDesc : '');
|
};
|
|
/**
|
* The InfoRegionComponent class
|
*
|
* @private
|
* @class
|
* @name Highcharts.InfoRegionComponent
|
* @param {Highcharts.Chart} chart
|
* Chart object
|
*/
|
var InfoRegionComponent = function InfoRegionComponent(chart) {
|
this.initBase(chart);
|
this.init();
|
};
|
InfoRegionComponent.prototype = new _AccessibilityComponent2.default();
|
_Globals2.default.extend(InfoRegionComponent.prototype, /** @lends Highcharts.InfoRegionComponent */{ // eslint-disable-line
|
|
/**
|
* Init the component
|
* @private
|
*/
|
init: function init() {
|
// Add ID and summary attr to table HTML
|
var chart = this.chart,
|
component = this;
|
this.addEvent(chart, 'afterGetTable', function (e) {
|
if (chart.options.accessibility.enabled) {
|
component.tableAnchor.setAttribute('aria-expanded', true);
|
e.html = e.html.replace('<table ', '<table tabindex="0" summary="' + chart.langFormat('accessibility.tableSummary', { chart: chart }) + '"');
|
}
|
});
|
|
// Focus table after viewing
|
this.addEvent(chart, 'afterViewData', function (tableDiv) {
|
// Use small delay to give browsers & AT time to register new table
|
setTimeout(function () {
|
var table = tableDiv && tableDiv.getElementsByTagName('table')[0];
|
if (table && table.focus) {
|
table.focus();
|
}
|
}, 300);
|
});
|
},
|
|
/**
|
* Called on first render/updates to the chart, including options changes.
|
*/
|
onChartUpdate: function onChartUpdate() {
|
// Create/update the screen reader region
|
var chart = this.chart,
|
a11yOptions = chart.options.accessibility,
|
hiddenSectionId = 'highcharts-information-region-' + chart.index,
|
hiddenSection = this.screenReaderRegion = this.screenReaderRegion || this.createElement('div'),
|
tableShortcut = this.tableHeading = this.tableHeading || this.createElement('h6'),
|
tableShortcutAnchor = this.tableAnchor = this.tableAnchor || this.createElement('a'),
|
chartHeading = this.chartHeading = this.chartHeading || this.createElement('h6');
|
|
hiddenSection.setAttribute('id', hiddenSectionId);
|
if (a11yOptions.landmarkVerbosity === 'all') {
|
hiddenSection.setAttribute('role', 'region');
|
}
|
hiddenSection.setAttribute('aria-label', chart.langFormat('accessibility.screenReaderRegionLabel', { chart: chart }));
|
|
hiddenSection.innerHTML = a11yOptions.screenReaderSectionFormatter ? a11yOptions.screenReaderSectionFormatter(chart) : this.defaultScreenReaderSectionFormatter(chart);
|
|
// Add shortcut to data table if export-data is loaded
|
if (chart.getCSV && chart.options.accessibility.addTableShortcut) {
|
var tableId = 'highcharts-data-table-' + chart.index;
|
tableShortcutAnchor.innerHTML = chart.langFormat('accessibility.viewAsDataTable', { chart: chart });
|
tableShortcutAnchor.href = '#' + tableId;
|
// Make this unreachable by user tabbing
|
tableShortcutAnchor.setAttribute('tabindex', '-1');
|
tableShortcutAnchor.setAttribute('role', 'button');
|
tableShortcutAnchor.setAttribute('aria-expanded', false);
|
tableShortcutAnchor.onclick = chart.options.accessibility.onTableAnchorClick || function () {
|
chart.viewData();
|
};
|
tableShortcut.appendChild(tableShortcutAnchor);
|
hiddenSection.appendChild(tableShortcut);
|
}
|
|
// Note: JAWS seems to refuse to read aria-label on the container, so
|
// add an h6 element as title for the chart.
|
chartHeading.innerHTML = chart.langFormat('accessibility.chartHeading', { chart: chart });
|
chartHeading.setAttribute('aria-hidden', false);
|
chart.renderTo.insertBefore(chartHeading, chart.renderTo.firstChild);
|
chart.renderTo.insertBefore(hiddenSection, chart.renderTo.firstChild);
|
this.unhideElementFromScreenReaders(hiddenSection);
|
|
// Visually hide the section and the chart heading
|
merge(true, chartHeading.style, this.hiddenStyle);
|
merge(true, hiddenSection.style, this.hiddenStyle);
|
},
|
|
/**
|
* The default formatter for the screen reader section.
|
* @private
|
*/
|
defaultScreenReaderSectionFormatter: function defaultScreenReaderSectionFormatter() {
|
var chart = this.chart,
|
options = chart.options,
|
chartTypes = chart.types,
|
axesDesc = this.getAxesDescription();
|
|
return '<h5>' + (options.accessibility.typeDescription || chart.getTypeDescription(chartTypes)) + '</h5>' + (options.subtitle && options.subtitle.text ? '<div>' + this.htmlencode(options.subtitle.text) + '</div>' : '') + (options.accessibility.description ? '<div>' + options.accessibility.description + '</div>' : '') + (axesDesc.xAxis ? '<div>' + axesDesc.xAxis + '</div>' : '') + (axesDesc.yAxis ? '<div>' + axesDesc.yAxis + '</div>' : '');
|
},
|
|
/**
|
* Return object with text description of each of the chart's axes.
|
* @private
|
* @return {object}
|
*/
|
getAxesDescription: function getAxesDescription() {
|
var chart = this.chart,
|
component = this,
|
xAxes = chart.xAxis,
|
|
// Figure out when to show axis info in the region
|
showXAxes = xAxes.length > 1 || xAxes[0] && pick(xAxes[0].options.accessibility && xAxes[0].options.accessibility.enabled, !chart.angular && chart.hasCartesianSeries && chart.types.indexOf('map') < 0),
|
yAxes = chart.yAxis,
|
showYAxes = yAxes.length > 1 || yAxes[0] && pick(yAxes[0].options.accessibility && yAxes[0].options.accessibility.enabled, chart.hasCartesianSeries && chart.types.indexOf('map') < 0),
|
desc = {};
|
|
if (showXAxes) {
|
desc.xAxis = chart.langFormat('accessibility.axis.xAxisDescription' + (xAxes.length > 1 ? 'Plural' : 'Singular'), {
|
chart: chart,
|
names: chart.xAxis.map(function (axis) {
|
return axis.getDescription();
|
}),
|
ranges: chart.xAxis.map(function (axis) {
|
return component.getAxisRangeDescription(axis);
|
}),
|
numAxes: xAxes.length
|
});
|
}
|
|
if (showYAxes) {
|
desc.yAxis = chart.langFormat('accessibility.axis.yAxisDescription' + (yAxes.length > 1 ? 'Plural' : 'Singular'), {
|
chart: chart,
|
names: chart.yAxis.map(function (axis) {
|
return axis.getDescription();
|
}),
|
ranges: chart.yAxis.map(function (axis) {
|
return component.getAxisRangeDescription(axis);
|
}),
|
numAxes: yAxes.length
|
});
|
}
|
|
return desc;
|
},
|
|
/**
|
* Return string with text description of the axis range.
|
* @private
|
* @param {Highcharts.Axis} axis The axis to get range desc of.
|
* @return {string} A string with the range description for the axis.
|
*/
|
getAxisRangeDescription: function getAxisRangeDescription(axis) {
|
var chart = this.chart,
|
axisOptions = axis.options || {};
|
|
// Handle overridden range description
|
if (axisOptions.accessibility && axisOptions.accessibility.rangeDescription !== undefined) {
|
return axisOptions.accessibility.rangeDescription;
|
}
|
|
// Handle category axes
|
if (axis.categories) {
|
return chart.langFormat('accessibility.axis.rangeCategories', {
|
chart: chart,
|
axis: axis,
|
numCategories: axis.dataMax - axis.dataMin + 1
|
});
|
}
|
|
// Use range, not from-to?
|
if (axis.isDatetimeAxis && (axis.min === 0 || axis.dataMin === 0)) {
|
var range = {},
|
rangeUnit = 'Seconds';
|
range.Seconds = (axis.max - axis.min) / 1000;
|
range.Minutes = range.Seconds / 60;
|
range.Hours = range.Minutes / 60;
|
range.Days = range.Hours / 24;
|
['Minutes', 'Hours', 'Days'].forEach(function (unit) {
|
if (range[unit] > 2) {
|
rangeUnit = unit;
|
}
|
});
|
range.value = range[rangeUnit].toFixed(rangeUnit !== 'Seconds' && rangeUnit !== 'Minutes' ? 1 : 0 // Use decimals for days/hours
|
);
|
|
// We have the range and the unit to use, find the desc format
|
return chart.langFormat('accessibility.axis.timeRange' + rangeUnit, {
|
chart: chart,
|
axis: axis,
|
range: range.value.replace('.0', '')
|
});
|
}
|
|
// Just use from and to.
|
// We have the range and the unit to use, find the desc format
|
var a11yOptions = chart.options.accessibility;
|
return chart.langFormat('accessibility.axis.rangeFromTo', {
|
chart: chart,
|
axis: axis,
|
rangeFrom: axis.isDatetimeAxis ? chart.time.dateFormat(a11yOptions.axisRangeDateFormat, axis.min) : axis.min,
|
rangeTo: axis.isDatetimeAxis ? chart.time.dateFormat(a11yOptions.axisRangeDateFormat, axis.max) : axis.max
|
});
|
}
|
|
});
|
|
exports.default = InfoRegionComponent;
|