NGToolsCSharp/NGTools/Scripts/Highcharts-7.1.1/code/es-modules/modules/accessibility/components/InfoRegionComponent.js
2024-09-13 16:44:30 +08:00

380 lines
11 KiB
JavaScript

/* *
*
* (c) 2009-2019 Øystein Moseng
*
* Accessibility component for chart info region and table.
*
* License: www.highcharts.com/license
*
* */
'use strict';
import H from '../../../parts/Globals.js';
import AccessibilityComponent from '../AccessibilityComponent.js';
var merge = H.merge,
pick = H.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.
*/
H.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 (chart) {
this.initBase(chart);
this.init();
};
InfoRegionComponent.prototype = new AccessibilityComponent();
H.extend(InfoRegionComponent.prototype, /** @lends Highcharts.InfoRegionComponent */ { // eslint-disable-line
/**
* Init the component
* @private
*/
init: function () {
// 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 () {
// 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 () {
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 () {
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 (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
}
);
}
});
export default InfoRegionComponent;