Carga
This commit is contained in:
2025-04-17 00:35:33 -06:00
parent 4977462629
commit 67fc72aed5
1333 changed files with 1077639 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Handle forcing series markers.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import U from '../../../Core/Utilities.js';
var addEvent = U.addEvent, merge = U.merge;
/* *
*
* Composition
*
* */
var ForcedMarkersComposition;
(function (ForcedMarkersComposition) {
/* *
*
* Declarations
*
* */
/* *
*
* Compositions
*
* */
var composedClasses = [];
/* *
*
* Functions
*
* */
/* eslint-disable valid-jsdoc */
/**
* @private
*/
function compose(SeriesClass) {
if (composedClasses.indexOf(SeriesClass) === -1) {
composedClasses.push(SeriesClass);
addEvent(SeriesClass, 'afterSetOptions', seriesOnAfterSetOptions);
addEvent(SeriesClass, 'render', seriesOnRender);
addEvent(SeriesClass, 'afterRender', seriesOnAfterRender);
}
}
ForcedMarkersComposition.compose = compose;
/**
* @private
*/
function forceZeroOpacityMarkerOptions(options) {
merge(true, options, {
marker: {
enabled: true,
states: {
normal: {
opacity: 0
}
}
}
});
}
/**
* @private
*/
function getPointMarkerOpacity(pointOptions) {
return pointOptions.marker.states &&
pointOptions.marker.states.normal &&
pointOptions.marker.states.normal.opacity;
}
/**
* @private
*/
function handleForcePointMarkers(series) {
var i = series.points.length;
while (i--) {
var point = series.points[i];
var pointOptions = point.options;
var hadForcedMarker = point.hasForcedA11yMarker;
delete point.hasForcedA11yMarker;
if (pointOptions.marker) {
var isStillForcedMarker = hadForcedMarker &&
getPointMarkerOpacity(pointOptions) === 0;
if (pointOptions.marker.enabled && !isStillForcedMarker) {
unforcePointMarkerOptions(pointOptions);
point.hasForcedA11yMarker = false;
}
else if (pointOptions.marker.enabled === false) {
forceZeroOpacityMarkerOptions(pointOptions);
point.hasForcedA11yMarker = true;
}
}
}
}
/**
* @private
*/
function hasIndividualPointMarkerOptions(series) {
return !!(series._hasPointMarkers &&
series.points &&
series.points.length);
}
/**
* @private
*/
function isWithinDescriptionThreshold(series) {
var a11yOptions = series.chart.options.accessibility;
return series.points.length <
a11yOptions.series.pointDescriptionEnabledThreshold ||
a11yOptions.series
.pointDescriptionEnabledThreshold === false;
}
/**
* Process marker graphics after render
* @private
*/
function seriesOnAfterRender() {
var series = this;
// For styled mode the rendered graphic does not reflect the style
// options, and we need to add/remove classes to achieve the same.
if (series.chart.styledMode) {
if (series.markerGroup) {
series.markerGroup[series.a11yMarkersForced ? 'addClass' : 'removeClass']('highcharts-a11y-markers-hidden');
}
// Do we need to handle individual points?
if (hasIndividualPointMarkerOptions(series)) {
series.points.forEach(function (point) {
if (point.graphic) {
point.graphic[point.hasForcedA11yMarker ?
'addClass' : 'removeClass']('highcharts-a11y-marker-hidden');
point.graphic[point.hasForcedA11yMarker === false ?
'addClass' :
'removeClass']('highcharts-a11y-marker-visible');
}
});
}
}
}
/**
* Keep track of options to reset markers to if no longer forced.
* @private
*/
function seriesOnAfterSetOptions(e) {
this.resetA11yMarkerOptions = merge(e.options.marker || {}, this.userOptions.marker || {});
}
/**
* Keep track of forcing markers.
* @private
*/
function seriesOnRender() {
var series = this, options = series.options;
if (shouldForceMarkers(series)) {
if (options.marker && options.marker.enabled === false) {
series.a11yMarkersForced = true;
forceZeroOpacityMarkerOptions(series.options);
}
if (hasIndividualPointMarkerOptions(series)) {
handleForcePointMarkers(series);
}
}
else if (series.a11yMarkersForced) {
delete series.a11yMarkersForced;
unforceSeriesMarkerOptions(series);
delete series.resetA11yMarkerOptions;
}
}
/**
* @private
*/
function shouldForceMarkers(series) {
var chart = series.chart, chartA11yEnabled = chart.options.accessibility.enabled, seriesA11yEnabled = (series.options.accessibility &&
series.options.accessibility.enabled) !== false;
return (chartA11yEnabled &&
seriesA11yEnabled &&
isWithinDescriptionThreshold(series));
}
/**
* @private
*/
function unforcePointMarkerOptions(pointOptions) {
merge(true, pointOptions.marker, {
states: {
normal: {
opacity: getPointMarkerOpacity(pointOptions) || 1
}
}
});
}
/**
* Reset markers to normal
* @private
*/
function unforceSeriesMarkerOptions(series) {
var resetMarkerOptions = series.resetA11yMarkerOptions;
if (resetMarkerOptions) {
var originalOpactiy = resetMarkerOptions.states &&
resetMarkerOptions.states.normal &&
resetMarkerOptions.states.normal.opacity;
series.update({
marker: {
enabled: resetMarkerOptions.enabled,
states: {
normal: { opacity: originalOpactiy }
}
}
});
}
}
})(ForcedMarkersComposition || (ForcedMarkersComposition = {}));
/* *
*
* Default Export
*
* */
export default ForcedMarkersComposition;

View File

@@ -0,0 +1,324 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Handle announcing new data for a chart.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../../../Core/Globals.js';
import U from '../../../Core/Utilities.js';
var addEvent = U.addEvent, defined = U.defined;
import Announcer from '../../Utils/Announcer.js';
import ChartUtilities from '../../Utils/ChartUtilities.js';
var getChartTitle = ChartUtilities.getChartTitle;
import EventProvider from '../../Utils/EventProvider.js';
import SeriesDescriber from './SeriesDescriber.js';
var defaultPointDescriptionFormatter = SeriesDescriber.defaultPointDescriptionFormatter, defaultSeriesDescriptionFormatter = SeriesDescriber.defaultSeriesDescriptionFormatter;
/* *
*
* Functions
*
* */
/* eslint-disable valid-jsdoc */
/**
* @private
*/
function chartHasAnnounceEnabled(chart) {
return !!chart.options.accessibility.announceNewData.enabled;
}
/**
* @private
*/
function findPointInDataArray(point) {
var candidates = point.series.data.filter(function (candidate) { return (point.x === candidate.x && point.y === candidate.y); });
return candidates.length === 1 ? candidates[0] : point;
}
/**
* Get array of unique series from two arrays
* @private
*/
function getUniqueSeries(arrayA, arrayB) {
var uniqueSeries = (arrayA || []).concat(arrayB || []).reduce(function (acc, cur) {
acc[cur.name + cur.index] = cur;
return acc;
}, {});
return Object
.keys(uniqueSeries)
.map(function (ix) { return uniqueSeries[ix]; });
}
/* *
*
* Class
*
* */
/**
* @private
* @class
*/
var NewDataAnnouncer = /** @class */ (function () {
/* *
*
* Constructor
*
* */
function NewDataAnnouncer(chart) {
/* *
*
* Public
*
* */
this.announcer = void 0;
this.dirty = {
allSeries: {}
};
this.eventProvider = void 0;
this.lastAnnouncementTime = 0;
this.chart = chart;
}
/* *
*
* Functions
*
* */
/* eslint-disable valid-jsdoc */
/**
* Initialize the new data announcer.
* @private
*/
NewDataAnnouncer.prototype.init = function () {
var chart = this.chart;
var announceOptions = (chart.options.accessibility.announceNewData);
var announceType = announceOptions.interruptUser ?
'assertive' : 'polite';
this.lastAnnouncementTime = 0;
this.dirty = {
allSeries: {}
};
this.eventProvider = new EventProvider();
this.announcer = new Announcer(chart, announceType);
this.addEventListeners();
};
/**
* Remove traces of announcer.
* @private
*/
NewDataAnnouncer.prototype.destroy = function () {
this.eventProvider.removeAddedEvents();
this.announcer.destroy();
};
/**
* Add event listeners for the announcer
* @private
*/
NewDataAnnouncer.prototype.addEventListeners = function () {
var announcer = this, chart = this.chart, e = this.eventProvider;
e.addEvent(chart, 'afterApplyDrilldown', function () {
announcer.lastAnnouncementTime = 0;
});
e.addEvent(chart, 'afterAddSeries', function (e) {
announcer.onSeriesAdded(e.series);
});
e.addEvent(chart, 'redraw', function () {
announcer.announceDirtyData();
});
};
/**
* On new data series added, update dirty list.
* @private
* @param {Highcharts.Series} series
*/
NewDataAnnouncer.prototype.onSeriesAdded = function (series) {
if (chartHasAnnounceEnabled(this.chart)) {
this.dirty.hasDirty = true;
this.dirty.allSeries[series.name + series.index] = series;
// Add it to newSeries storage unless we already have one
this.dirty.newSeries = defined(this.dirty.newSeries) ?
void 0 : series;
}
};
/**
* Gather what we know and announce the data to user.
* @private
*/
NewDataAnnouncer.prototype.announceDirtyData = function () {
var chart = this.chart, announcer = this;
if (chart.options.accessibility.announceNewData &&
this.dirty.hasDirty) {
var newPoint = this.dirty.newPoint;
// If we have a single new point, see if we can find it in the
// data array. Otherwise we can only pass through options to
// the description builder, and it is a bit sparse in info.
if (newPoint) {
newPoint = findPointInDataArray(newPoint);
}
this.queueAnnouncement(Object
.keys(this.dirty.allSeries)
.map(function (ix) {
return announcer.dirty.allSeries[ix];
}), this.dirty.newSeries, newPoint);
// Reset
this.dirty = {
allSeries: {}
};
}
};
/**
* Announce to user that there is new data.
* @private
* @param {Array<Highcharts.Series>} dirtySeries
* Array of series with new data.
* @param {Highcharts.Series} [newSeries]
* If a single new series was added, a reference to this series.
* @param {Highcharts.Point} [newPoint]
* If a single point was added, a reference to this point.
*/
NewDataAnnouncer.prototype.queueAnnouncement = function (dirtySeries, newSeries, newPoint) {
var _this = this;
var chart = this.chart;
var annOptions = chart.options.accessibility.announceNewData;
if (annOptions.enabled) {
var now = +new Date();
var dTime = now - this.lastAnnouncementTime;
var time = Math.max(0, annOptions.minAnnounceInterval - dTime);
// Add series from previously queued announcement.
var allSeries = getUniqueSeries(this.queuedAnnouncement && this.queuedAnnouncement.series, dirtySeries);
// Build message and announce
var message = this.buildAnnouncementMessage(allSeries, newSeries, newPoint);
if (message) {
// Is there already one queued?
if (this.queuedAnnouncement) {
clearTimeout(this.queuedAnnouncementTimer);
}
// Build the announcement
this.queuedAnnouncement = {
time: now,
message: message,
series: allSeries
};
// Queue the announcement
this.queuedAnnouncementTimer = setTimeout(function () {
if (_this && _this.announcer) {
_this.lastAnnouncementTime = +new Date();
_this.announcer.announce(_this.queuedAnnouncement.message);
delete _this.queuedAnnouncement;
delete _this.queuedAnnouncementTimer;
}
}, time);
}
}
};
/**
* Get announcement message for new data.
* @private
* @param {Array<Highcharts.Series>} dirtySeries
* Array of series with new data.
* @param {Highcharts.Series} [newSeries]
* If a single new series was added, a reference to this series.
* @param {Highcharts.Point} [newPoint]
* If a single point was added, a reference to this point.
*
* @return {string|null}
* The announcement message to give to user.
*/
NewDataAnnouncer.prototype.buildAnnouncementMessage = function (dirtySeries, newSeries, newPoint) {
var chart = this.chart, annOptions = chart.options.accessibility.announceNewData;
// User supplied formatter?
if (annOptions.announcementFormatter) {
var formatterRes = annOptions.announcementFormatter(dirtySeries, newSeries, newPoint);
if (formatterRes !== false) {
return formatterRes.length ? formatterRes : null;
}
}
// Default formatter - use lang options
var multiple = H.charts && H.charts.length > 1 ?
'Multiple' : 'Single', langKey = newSeries ? 'newSeriesAnnounce' + multiple :
newPoint ? 'newPointAnnounce' + multiple : 'newDataAnnounce', chartTitle = getChartTitle(chart);
return chart.langFormat('accessibility.announceNewData.' + langKey, {
chartTitle: chartTitle,
seriesDesc: newSeries ?
defaultSeriesDescriptionFormatter(newSeries) :
null,
pointDesc: newPoint ?
defaultPointDescriptionFormatter(newPoint) :
null,
point: newPoint,
series: newSeries
});
};
return NewDataAnnouncer;
}());
/* *
*
* Class Namespace
*
* */
(function (NewDataAnnouncer) {
/* *
*
* Declarations
*
* */
/* *
*
* Static Properties
*
* */
NewDataAnnouncer.composedClasses = [];
/* *
*
* Static Functions
*
* */
/**
* @private
*/
function compose(SeriesClass) {
if (NewDataAnnouncer.composedClasses.indexOf(SeriesClass) === -1) {
NewDataAnnouncer.composedClasses.push(SeriesClass);
addEvent(SeriesClass, 'addPoint', seriesOnAddPoint);
addEvent(SeriesClass, 'updatedData', seriesOnUpdatedData);
}
}
NewDataAnnouncer.compose = compose;
/**
* On new point added, update dirty list.
* @private
* @param {Highcharts.Point} point
*/
function seriesOnAddPoint(e) {
var chart = this.chart, newDataAnnouncer = this.newDataAnnouncer;
if (newDataAnnouncer &&
newDataAnnouncer.chart === chart &&
chartHasAnnounceEnabled(chart)) {
// Add it to newPoint storage unless we already have one
newDataAnnouncer.dirty.newPoint = (defined(newDataAnnouncer.dirty.newPoint) ?
void 0 :
e.point);
}
}
/**
* On new data in the series, make sure we add it to the dirty list.
* @private
* @param {Highcharts.Series} series
*/
function seriesOnUpdatedData() {
var chart = this.chart, newDataAnnouncer = this.newDataAnnouncer;
if (newDataAnnouncer &&
newDataAnnouncer.chart === chart &&
chartHasAnnounceEnabled(chart)) {
newDataAnnouncer.dirty.hasDirty = true;
newDataAnnouncer.dirty.allSeries[this.name + this.index] = this;
}
}
})(NewDataAnnouncer || (NewDataAnnouncer = {}));
/* *
*
* Default Export
*
* */
export default NewDataAnnouncer;

View File

@@ -0,0 +1,149 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Accessibility component for series and points.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import AccessibilityComponent from '../../AccessibilityComponent.js';
import ChartUtilities from '../../Utils/ChartUtilities.js';
var hideSeriesFromAT = ChartUtilities.hideSeriesFromAT;
import ForcedMarkers from './ForcedMarkers.js';
import NewDataAnnouncer from './NewDataAnnouncer.js';
import SeriesDescriber from './SeriesDescriber.js';
var describeSeries = SeriesDescriber.describeSeries;
import SeriesKeyboardNavigation from './SeriesKeyboardNavigation.js';
import Tooltip from '../../../Core/Tooltip.js';
/* *
*
* Class
*
* */
/**
* The SeriesComponent class
*
* @private
* @class
* @name Highcharts.SeriesComponent
*/
var SeriesComponent = /** @class */ (function (_super) {
__extends(SeriesComponent, _super);
function SeriesComponent() {
return _super !== null && _super.apply(this, arguments) || this;
}
/* *
*
* Static Functions
*
* */
/* eslint-disable valid-jsdoc */
/**
* @private
*/
SeriesComponent.compose = function (ChartClass, PointClass, SeriesClass) {
NewDataAnnouncer.compose(SeriesClass);
ForcedMarkers.compose(SeriesClass);
SeriesKeyboardNavigation.compose(ChartClass, PointClass, SeriesClass);
};
/* *
*
* Functions
*
* */
/**
* Init the component.
*/
SeriesComponent.prototype.init = function () {
this.newDataAnnouncer = new NewDataAnnouncer(this.chart);
this.newDataAnnouncer.init();
this.keyboardNavigation = new SeriesKeyboardNavigation(this.chart, this.keyCodes);
this.keyboardNavigation.init();
this.hideTooltipFromATWhenShown();
this.hideSeriesLabelsFromATWhenShown();
};
/**
* @private
*/
SeriesComponent.prototype.hideTooltipFromATWhenShown = function () {
var component = this;
this.addEvent(Tooltip, 'refresh', function () {
if (this.chart === component.chart &&
this.label &&
this.label.element) {
this.label.element.setAttribute('aria-hidden', true);
}
});
};
/**
* @private
*/
SeriesComponent.prototype.hideSeriesLabelsFromATWhenShown = function () {
this.addEvent(this.chart, 'afterDrawSeriesLabels', function () {
this.series.forEach(function (series) {
if (series.labelBySeries) {
series.labelBySeries.attr('aria-hidden', true);
}
});
});
};
/**
* Called on chart render. It is necessary to do this for render in case
* markers change on zoom/pixel density.
*/
SeriesComponent.prototype.onChartRender = function () {
var chart = this.chart;
chart.series.forEach(function (series) {
var shouldDescribeSeries = (series.options.accessibility &&
series.options.accessibility.enabled) !== false &&
series.visible;
if (shouldDescribeSeries) {
describeSeries(series);
}
else {
hideSeriesFromAT(series);
}
});
};
/**
* Get keyboard navigation handler for this component.
* @private
*/
SeriesComponent.prototype.getKeyboardNavigation = function () {
return this.keyboardNavigation.getKeyboardNavigationHandler();
};
/**
* Remove traces
* @private
*/
SeriesComponent.prototype.destroy = function () {
this.newDataAnnouncer.destroy();
this.keyboardNavigation.destroy();
};
return SeriesComponent;
}(AccessibilityComponent));
/* *
*
* Default Export
*
* */
export default SeriesComponent;

View File

@@ -0,0 +1,407 @@
/* *
*
* (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 F from '../../../Core/FormatUtilities.js';
var format = F.format, numberFormat = F.numberFormat;
import HTMLUtilities from '../../Utils/HTMLUtilities.js';
var reverseChildNodes = HTMLUtilities.reverseChildNodes, stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString;
import U from '../../../Core/Utilities.js';
var find = U.find, isNumber = U.isNumber, pick = U.pick, defined = U.defined;
/* *
*
* Functions
*
* */
/* 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;
}
/**
* Whether or not we should add a dummy point element in
* order to describe a point that has no graphic.
* @private
*/
function shouldAddDummyPoint(point) {
// Note: Sunburst series use isNull for hidden points on drilldown.
// Ignore these.
var series = point.series, chart = series && series.chart, isSunburst = series && series.is('sunburst'), isNull = point.isNull, shouldDescribeNull = chart &&
chart
.options.accessibility.point.describeNull;
return isNull && !isSunburst && shouldDescribeNull;
}
/**
* @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
*/
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
*/
function hasMorePointsThanDescriptionThreshold(series) {
var chartA11yOptions = series.chart.options.accessibility, threshold = (chartA11yOptions.series.pointDescriptionEnabledThreshold);
return !!(threshold !== false &&
series.points &&
series.points.length >= threshold);
}
/**
* @private
*/
function shouldSetScreenReaderPropsOnPoints(series) {
var seriesA11yOptions = series.options.accessibility || {};
return !hasMorePointsThanDescriptionThreshold(series) &&
!seriesA11yOptions.exposeAsGroupOnly;
}
/**
* @private
*/
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
*/
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
*/
function pointNumberToString(point, value) {
var series = point.series, chart = series.chart, a11yPointOptions = chart.options.accessibility.point || {}, seriesA11yPointOptions = series.options.accessibility &&
series.options.accessibility.point || {}, tooltipOptions = series.tooltipOptions || {}, lang = chart.options.lang;
if (isNumber(value)) {
return numberFormat(value, seriesA11yPointOptions.valueDecimals ||
a11yPointOptions.valueDecimals ||
tooltipOptions.valueDecimals ||
-1, lang.decimalPoint, lang.accessibility.thousandsSep || lang.thousandsSep);
}
return value;
}
/**
* @private
*/
function getSeriesDescriptionText(series) {
var seriesA11yOptions = series.options.accessibility || {}, descOpt = seriesA11yOptions.description;
return descOpt && series.chart.langFormat('accessibility.series.description', {
description: descOpt,
series: series
}) || '';
}
/**
* @private
*/
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 getPointA11yTimeDescription(point) {
var series = point.series, chart = series.chart, seriesA11yOptions = series.options.accessibility &&
series.options.accessibility.point || {}, a11yOptions = chart.options.accessibility.point || {}, dateXAxis = series.xAxis && series.xAxis.dateTime;
if (dateXAxis) {
var tooltipDateFormat = dateXAxis.getXDateFormat(point.x || 0, chart.options.tooltip.dateTimeLabelFormats), dateFormat = seriesA11yOptions.dateFormatter &&
seriesA11yOptions.dateFormatter(point) ||
a11yOptions.dateFormatter && a11yOptions.dateFormatter(point) ||
seriesA11yOptions.dateFormat ||
a11yOptions.dateFormat ||
tooltipDateFormat;
return chart.time.dateFormat(dateFormat, point.x || 0, void 0);
}
}
/**
* @private
*/
function getPointXDescription(point) {
var timeDesc = getPointA11yTimeDescription(point), xAxis = point.series.xAxis || {}, pointCategory = xAxis.categories && defined(point.category) &&
('' + point.category).replace('<br/>', ' '), canUseId = defined(point.id) &&
('' + point.id).indexOf('highcharts-') < 0, fallback = 'x, ' + point.x;
return point.name || timeDesc || pointCategory ||
(canUseId ? point.id : fallback);
}
/**
* @private
*/
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
*/
function getPointValue(point) {
var series = point.series, a11yPointOpts = series.chart.options.accessibility.point || {}, seriesA11yPointOpts = series.chart.options.accessibility &&
series.chart.options.accessibility.point || {}, tooltipOptions = series.tooltipOptions || {}, valuePrefix = seriesA11yPointOpts.valuePrefix ||
a11yPointOpts.valuePrefix ||
tooltipOptions.valuePrefix ||
'', valueSuffix = seriesA11yPointOpts.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
*/
function getPointValueDescription(point) {
var series = point.series, chart = series.chart, seriesA11yOptions = series.options.accessibility, seriesValueDescFormat = seriesA11yOptions && seriesA11yOptions.point &&
seriesA11yOptions.point.valueDescriptionFormat, pointValueDescriptionFormat = seriesValueDescFormat ||
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
*/
function defaultPointDescriptionFormatter(point) {
var series = point.series, shouldExposeSeriesName = series.chart.series.length > 1 ||
series.options.name, valText = getPointValueDescription(point), description = point.options && point.options.accessibility &&
point.options.accessibility.description, userDescText = description ? ' ' + description : '', seriesNameText = shouldExposeSeriesName ? ' ' + 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 || {}, seriesPointA11yOptions = series.options.accessibility &&
series.options.accessibility.point || {}, label = stripHTMLTags(seriesPointA11yOptions.descriptionFormatter &&
seriesPointA11yOptions.descriptionFormatter(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), shouldDescribeNullPoints = series.chart.options.accessibility
.point.describeNull;
if (setScreenReaderProps || setKeyboardProps) {
series.points.forEach(function (point) {
var pointEl = point.graphic && point.graphic.element ||
shouldAddDummyPoint(point) && addDummyPointElement(point), pointA11yDisabled = (point.options &&
point.options.accessibility &&
point.options.accessibility.enabled === false);
if (pointEl) {
if (point.isNull && !shouldDescribeNullPoints) {
pointEl.setAttribute('aria-hidden', true);
return;
}
// 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');
if (!series.chart.styledMode) {
pointEl.style.outline = 'none';
}
if (setScreenReaderProps && !pointA11yDisabled) {
setPointScreenReaderAttribs(point, pointEl);
}
else {
pointEl.setAttribute('aria-hidden', true);
}
}
});
}
}
/**
* Return string with information about series.
* @private
*/
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];
}, seriesNumber = series.index + 1, xAxisInfo = getSeriesAxisDescriptionText(series, 'xAxis'), yAxisInfo = getSeriesAxisDescriptionText(series, 'yAxis'), summaryContext = {
seriesNumber: seriesNumber,
series: series,
chart: chart
}, combinationSuffix = chartTypes.length > 1 ? 'Combination' : '', summary = chart.langFormat('accessibility.series.summary.' + series.type + combinationSuffix, summaryContext) || chart.langFormat('accessibility.series.summary.default' + combinationSuffix, summaryContext), axisDescription = (shouldDescribeAxis('yAxis') ? ' ' + yAxisInfo + '.' : '') + (shouldDescribeAxis('xAxis') ? ' ' + xAxisInfo + '.' : ''), formatStr = chart.options.accessibility.series.descriptionFormat || '';
return format(formatStr, {
seriesDescription: summary,
authorDescription: (description ? ' ' + description : ''),
axisDescription: axisDescription,
series: series,
chart: chart,
seriesNumber: seriesNumber
}, void 0);
}
/**
* 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 {
seriesElement.setAttribute('role', 'group');
}
seriesElement.setAttribute('tabindex', '-1');
if (!series.chart.styledMode) {
// Don't show browser outline on click, despite tabindex
seriesElement.style.outline = 'none';
}
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 cannot 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.removeAttribute('aria-label');
}
}
}
/* *
*
* Default Export
*
* */
var SeriesDescriber = {
defaultPointDescriptionFormatter: defaultPointDescriptionFormatter,
defaultSeriesDescriptionFormatter: defaultSeriesDescriptionFormatter,
describeSeries: describeSeries
};
export default SeriesDescriber;

View File

@@ -0,0 +1,707 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Handle keyboard navigation for series.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Point from '../../../Core/Series/Point.js';
import Series from '../../../Core/Series/Series.js';
import SeriesRegistry from '../../../Core/Series/SeriesRegistry.js';
var seriesTypes = SeriesRegistry.seriesTypes;
import H from '../../../Core/Globals.js';
var doc = H.doc;
import U from '../../../Core/Utilities.js';
var defined = U.defined, fireEvent = U.fireEvent;
import KeyboardNavigationHandler from '../../KeyboardNavigationHandler.js';
import EventProvider from '../../Utils/EventProvider.js';
import ChartUtilities from '../../Utils/ChartUtilities.js';
var getPointFromXY = ChartUtilities.getPointFromXY, getSeriesFromName = ChartUtilities.getSeriesFromName, scrollToPoint = ChartUtilities.scrollToPoint;
/* *
*
* Functions
*
* */
/* eslint-disable valid-jsdoc */
/**
* Get the index of a point in a series. This is needed when using e.g. data
* grouping.
*
* @private
* @function getPointIndex
* @param {Highcharts.AccessibilityPoint} point
* The point to find index of.
* @return {number|undefined}
* The index in the series.points array of the point.
*/
function getPointIndex(point) {
var index = point.index, points = point.series.points;
var i = points.length;
if (points[index] !== point) {
while (i--) {
if (points[i] === point) {
return i;
}
}
}
else {
return index;
}
}
/**
* Determine if series navigation should be skipped
* @private
*/
function isSkipSeries(series) {
var a11yOptions = series.chart.options.accessibility, seriesNavOptions = a11yOptions.keyboardNavigation.seriesNavigation, seriesA11yOptions = series.options.accessibility || {}, seriesKbdNavOptions = seriesA11yOptions.keyboardNavigation;
return seriesKbdNavOptions && seriesKbdNavOptions.enabled === false ||
seriesA11yOptions.enabled === false ||
series.options.enableMouseTracking === false || // #8440
!series.visible ||
// Skip all points in a series where pointNavigationEnabledThreshold is
// reached
(seriesNavOptions.pointNavigationEnabledThreshold &&
seriesNavOptions.pointNavigationEnabledThreshold <=
series.points.length);
}
/**
* Determine if navigation for a point should be skipped
* @private
*/
function isSkipPoint(point) {
var a11yOptions = point.series.chart.options.accessibility;
var pointA11yDisabled = (point.options.accessibility &&
point.options.accessibility.enabled === false);
return point.isNull &&
a11yOptions.keyboardNavigation.seriesNavigation.skipNullPoints ||
point.visible === false ||
point.isInside === false ||
pointA11yDisabled ||
isSkipSeries(point.series);
}
/**
* Get the first point that is not a skip point in this series.
* @private
*/
function getFirstValidPointInSeries(series) {
var points = series.points || [], len = points.length;
for (var i = 0; i < len; ++i) {
if (!isSkipPoint(points[i])) {
return points[i];
}
}
return null;
}
/**
* Get the first point that is not a skip point in this chart.
* @private
*/
function getFirstValidPointInChart(chart) {
var series = chart.series || [], len = series.length;
for (var i = 0; i < len; ++i) {
if (!isSkipSeries(series[i])) {
var point = getFirstValidPointInSeries(series[i]);
if (point) {
return point;
}
}
}
return null;
}
/**
* @private
*/
function highlightLastValidPointInChart(chart) {
var numSeries = chart.series.length;
var i = numSeries, res = false;
while (i--) {
chart.highlightedPoint = chart.series[i].points[chart.series[i].points.length - 1];
// Highlight first valid point in the series will also
// look backwards. It always starts from currently
// highlighted point.
res = chart.series[i].highlightNextValidPoint();
if (res) {
break;
}
}
return res;
}
/**
* After drilling down/up, we need to set focus to the first point for
* screen readers and keyboard nav.
* @private
*/
function updateChartFocusAfterDrilling(chart) {
var point = getFirstValidPointInChart(chart);
if (point) {
point.highlight(false); // Do not visually highlight
}
}
/**
* Highlight the first point in chart that is not a skip point
* @private
*/
function highlightFirstValidPointInChart(chart) {
delete chart.highlightedPoint;
var point = getFirstValidPointInChart(chart);
return point ? point.highlight() : false;
}
/* *
*
* Class
*
* */
/**
* @private
* @class
* @name Highcharts.SeriesKeyboardNavigation
*/
var SeriesKeyboardNavigation = /** @class */ (function () {
/* *
*
* Constructor
*
* */
function SeriesKeyboardNavigation(chart, keyCodes) {
this.keyCodes = keyCodes;
this.chart = chart;
}
/* *
*
* Functions
*
* */
/* eslint-disable valid-jsdoc */
/**
* Init the keyboard navigation
*/
SeriesKeyboardNavigation.prototype.init = function () {
var keyboardNavigation = this, chart = this.chart, e = this.eventProvider = new EventProvider();
e.addEvent(Series, 'destroy', function () {
return keyboardNavigation.onSeriesDestroy(this);
});
e.addEvent(chart, 'afterApplyDrilldown', function () {
updateChartFocusAfterDrilling(this);
});
e.addEvent(chart, 'drilldown', function (e) {
var point = e.point, series = point.series;
keyboardNavigation.lastDrilledDownPoint = {
x: point.x,
y: point.y,
seriesName: series ? series.name : ''
};
});
e.addEvent(chart, 'drillupall', function () {
setTimeout(function () {
keyboardNavigation.onDrillupAll();
}, 10);
});
// Heatmaps et al. alter z-index in setState, causing elements
// to lose focus
e.addEvent(Point, 'afterSetState', function () {
var point = this;
var pointEl = point.graphic && point.graphic.element;
var focusedElement = doc.activeElement;
// VO brings focus with it to container, causing series nav to run.
// If then navigating with virtual cursor, it is possible to leave
// keyboard nav module state on the data points and still activate
// proxy buttons.
var focusedElClassName = (focusedElement && focusedElement.getAttribute('class'));
var isProxyFocused = focusedElClassName &&
focusedElClassName.indexOf('highcharts-a11y-proxy-button') > -1;
if (chart.highlightedPoint === point &&
focusedElement !== pointEl &&
!isProxyFocused &&
pointEl &&
pointEl.focus) {
pointEl.focus();
}
});
};
/**
* After drillup we want to find the point that was drilled down to and
* highlight it.
* @private
*/
SeriesKeyboardNavigation.prototype.onDrillupAll = function () {
var last = this.lastDrilledDownPoint, chart = this.chart, series = last && getSeriesFromName(chart, last.seriesName);
var point;
if (last && series && defined(last.x) && defined(last.y)) {
point = getPointFromXY(series, last.x, last.y);
}
point = point || getFirstValidPointInChart(chart);
// Container focus can be lost on drillup due to deleted elements.
if (chart.container) {
chart.container.focus();
}
if (point && point.highlight) {
point.highlight(false); // Do not visually highlight
}
};
/**
* @private
*/
SeriesKeyboardNavigation.prototype.getKeyboardNavigationHandler = function () {
var keyboardNavigation = this, keys = this.keyCodes, chart = this.chart, inverted = chart.inverted;
return new KeyboardNavigationHandler(chart, {
keyCodeMap: [
[inverted ? [keys.up, keys.down] : [keys.left, keys.right],
function (keyCode) {
return keyboardNavigation.onKbdSideways(this, keyCode);
}],
[inverted ? [keys.left, keys.right] : [keys.up, keys.down],
function (keyCode) {
return keyboardNavigation.onKbdVertical(this, keyCode);
}],
[[keys.enter, keys.space],
function (keyCode, event) {
var point = chart.highlightedPoint;
if (point) {
event.point = point;
fireEvent(point.series, 'click', event);
point.firePointEvent('click');
}
return this.response.success;
}],
[[keys.home],
function () {
highlightFirstValidPointInChart(chart);
return this.response.success;
}],
[[keys.end],
function () {
highlightLastValidPointInChart(chart);
return this.response.success;
}],
[[keys.pageDown, keys.pageUp],
function (keyCode) {
chart.highlightAdjacentSeries(keyCode === keys.pageDown);
return this.response.success;
}]
],
init: function () {
return keyboardNavigation.onHandlerInit(this);
},
validate: function () {
return !!getFirstValidPointInChart(chart);
},
terminate: function () {
return keyboardNavigation.onHandlerTerminate();
}
});
};
/**
* @private
* @param {Highcharts.KeyboardNavigationHandler} handler
* @param {number} keyCode
* @return {number}
* response
*/
SeriesKeyboardNavigation.prototype.onKbdSideways = function (handler, keyCode) {
var keys = this.keyCodes, isNext = keyCode === keys.right || keyCode === keys.down;
return this.attemptHighlightAdjacentPoint(handler, isNext);
};
/**
* When keyboard navigation inits.
* @private
* @param {Highcharts.KeyboardNavigationHandler} handler The handler object
* @return {number}
* response
*/
SeriesKeyboardNavigation.prototype.onHandlerInit = function (handler) {
var chart = this.chart, kbdNavOptions = chart.options.accessibility.keyboardNavigation;
if (kbdNavOptions.seriesNavigation.rememberPointFocus &&
chart.highlightedPoint) {
chart.highlightedPoint.highlight();
}
else {
highlightFirstValidPointInChart(chart);
}
return handler.response.success;
};
/**
* @private
* @param {Highcharts.KeyboardNavigationHandler} handler
* @param {number} keyCode
* @return {number}
* response
*/
SeriesKeyboardNavigation.prototype.onKbdVertical = function (handler, keyCode) {
var chart = this.chart, keys = this.keyCodes, isNext = keyCode === keys.down || keyCode === keys.right, navOptions = chart.options.accessibility.keyboardNavigation
.seriesNavigation;
// Handle serialized mode, act like left/right
if (navOptions.mode && navOptions.mode === 'serialize') {
return this.attemptHighlightAdjacentPoint(handler, isNext);
}
// Normal mode, move between series
var highlightMethod = (chart.highlightedPoint &&
chart.highlightedPoint.series.keyboardMoveVertical) ?
'highlightAdjacentPointVertical' :
'highlightAdjacentSeries';
chart[highlightMethod](isNext);
return handler.response.success;
};
/**
* @private
*/
SeriesKeyboardNavigation.prototype.onHandlerTerminate = function () {
var chart = this.chart, kbdNavOptions = chart.options.accessibility.keyboardNavigation;
if (chart.tooltip) {
chart.tooltip.hide(0);
}
var hoverSeries = (chart.highlightedPoint && chart.highlightedPoint.series);
if (hoverSeries && hoverSeries.onMouseOut) {
hoverSeries.onMouseOut();
}
if (chart.highlightedPoint && chart.highlightedPoint.onMouseOut) {
chart.highlightedPoint.onMouseOut();
}
if (!kbdNavOptions.seriesNavigation.rememberPointFocus) {
delete chart.highlightedPoint;
}
};
/**
* Function that attempts to highlight next/prev point. Handles wrap around.
* @private
*/
SeriesKeyboardNavigation.prototype.attemptHighlightAdjacentPoint = function (handler, directionIsNext) {
var chart = this.chart, wrapAround = chart.options.accessibility.keyboardNavigation
.wrapAround, highlightSuccessful = chart.highlightAdjacentPoint(directionIsNext);
if (!highlightSuccessful) {
if (wrapAround && (directionIsNext ?
highlightFirstValidPointInChart(chart) :
highlightLastValidPointInChart(chart))) {
return handler.response.success;
}
return handler.response[directionIsNext ? 'next' : 'prev'];
}
return handler.response.success;
};
/**
* @private
*/
SeriesKeyboardNavigation.prototype.onSeriesDestroy = function (series) {
var chart = this.chart, currentHighlightedPointDestroyed = chart.highlightedPoint &&
chart.highlightedPoint.series === series;
if (currentHighlightedPointDestroyed) {
delete chart.highlightedPoint;
if (chart.focusElement) {
chart.focusElement.removeFocusBorder();
}
}
};
/**
* @private
*/
SeriesKeyboardNavigation.prototype.destroy = function () {
this.eventProvider.removeAddedEvents();
};
return SeriesKeyboardNavigation;
}());
/* *
*
* Class Namespace
*
* */
(function (SeriesKeyboardNavigation) {
/* *
*
* Declarations
*
* */
/* *
*
* Constants
*
* */
var composedClasses = [];
/* *
*
* Functions
*
* */
/**
* Function to highlight next/previous point in chart.
*
* @private
* @function Highcharts.Chart#highlightAdjacentPoint
*
* @param {boolean} next
* Flag for the direction.
*
* @return {Highcharts.Point|boolean}
* Returns highlighted point on success, false on failure (no adjacent point
* to highlight in chosen direction).
*/
function chartHighlightAdjacentPoint(next) {
var chart = this, series = chart.series, curPoint = chart.highlightedPoint, curPointIndex = curPoint && getPointIndex(curPoint) || 0, curPoints = curPoint && curPoint.series.points || [], lastSeries = chart.series && chart.series[chart.series.length - 1], lastPoint = lastSeries &&
lastSeries.points &&
lastSeries.points[lastSeries.points.length - 1];
var newSeries, newPoint;
// If no points, return false
if (!series[0] || !series[0].points) {
return false;
}
if (!curPoint) {
// No point is highlighted yet. Try first/last point depending on
// move direction
newPoint = next ? series[0].points[0] : lastPoint;
}
else {
// We have a highlighted point. Grab next/prev point & series.
newSeries = series[curPoint.series.index + (next ? 1 : -1)];
newPoint = curPoints[curPointIndex + (next ? 1 : -1)];
if (!newPoint && newSeries) {
// Done with this series, try next one
newPoint = newSeries.points[next ? 0 : newSeries.points.length - 1];
}
// If there is no adjacent point, we return false
if (!newPoint) {
return false;
}
}
// Recursively skip points
if (isSkipPoint(newPoint)) {
// If we skip this whole series, move to the end of the series
// before we recurse, just to optimize
newSeries = newPoint.series;
if (isSkipSeries(newSeries)) {
chart.highlightedPoint = next ?
newSeries.points[newSeries.points.length - 1] :
newSeries.points[0];
}
else {
// Otherwise, just move one point
chart.highlightedPoint = newPoint;
}
// Retry
return chart.highlightAdjacentPoint(next);
}
// There is an adjacent point, highlight it
return newPoint.highlight();
}
/**
* Highlight the closest point vertically.
* @private
*/
function chartHighlightAdjacentPointVertical(down) {
var curPoint = this.highlightedPoint;
var minDistance = Infinity, bestPoint;
if (!defined(curPoint.plotX) || !defined(curPoint.plotY)) {
return false;
}
this.series.forEach(function (series) {
if (isSkipSeries(series)) {
return;
}
series.points.forEach(function (point) {
if (!defined(point.plotY) || !defined(point.plotX) ||
point === curPoint) {
return;
}
var yDistance = point.plotY - curPoint.plotY;
var width = Math.abs(point.plotX - curPoint.plotX), distance = Math.abs(yDistance) * Math.abs(yDistance) +
width * width * 4; // Weigh horizontal distance highly
// Reverse distance number if axis is reversed
if (series.yAxis && series.yAxis.reversed) {
yDistance *= -1;
}
if (yDistance <= 0 && down || yDistance >= 0 && !down ||
distance < 5 || // Points in same spot => infinite loop
isSkipPoint(point)) {
return;
}
if (distance < minDistance) {
minDistance = distance;
bestPoint = point;
}
});
});
return bestPoint ? bestPoint.highlight() : false;
}
/**
* Highlight next/previous series in chart. Returns false if no adjacent
* series in the direction, otherwise returns new highlighted point.
* @private
*/
function chartHighlightAdjacentSeries(down) {
var chart = this, curPoint = chart.highlightedPoint, lastSeries = chart.series && chart.series[chart.series.length - 1], lastPoint = lastSeries && lastSeries.points &&
lastSeries.points[lastSeries.points.length - 1];
var newSeries, newPoint, adjacentNewPoint;
// If no point is highlighted, highlight the first/last point
if (!chart.highlightedPoint) {
newSeries = down ? (chart.series && chart.series[0]) : lastSeries;
newPoint = down ?
(newSeries && newSeries.points && newSeries.points[0]) :
lastPoint;
return newPoint ? newPoint.highlight() : false;
}
newSeries = (chart.series[curPoint.series.index + (down ? -1 : 1)]);
if (!newSeries) {
return false;
}
// We have a new series in this direction, find the right point
// Weigh xDistance as counting much higher than Y distance
newPoint = getClosestPoint(curPoint, newSeries, 4);
if (!newPoint) {
return false;
}
// New series and point exists, but we might want to skip it
if (isSkipSeries(newSeries)) {
// Skip the series
newPoint.highlight();
// Try recurse
adjacentNewPoint = chart.highlightAdjacentSeries(down);
if (!adjacentNewPoint) {
// Recurse failed
curPoint.highlight();
return false;
}
// Recurse succeeded
return adjacentNewPoint;
}
// Highlight the new point or any first valid point back or forwards
// from it
newPoint.highlight();
return newPoint.series.highlightNextValidPoint();
}
/**
* @private
*/
function compose(ChartClass, PointClass, SeriesClass) {
if (composedClasses.indexOf(ChartClass) === -1) {
composedClasses.push(ChartClass);
var chartProto = ChartClass.prototype;
chartProto.highlightAdjacentPoint = chartHighlightAdjacentPoint;
chartProto.highlightAdjacentPointVertical = (chartHighlightAdjacentPointVertical);
chartProto.highlightAdjacentSeries = chartHighlightAdjacentSeries;
}
if (composedClasses.indexOf(PointClass) === -1) {
composedClasses.push(PointClass);
var pointProto = PointClass.prototype;
pointProto.highlight = pointHighlight;
}
if (composedClasses.indexOf(SeriesClass) === -1) {
composedClasses.push(SeriesClass);
var seriesProto = SeriesClass.prototype;
/**
* Set for which series types it makes sense to move to the closest
* point with up/down arrows, and which series types should just
* move to next series.
* @private
*/
seriesProto.keyboardMoveVertical = true;
[
'column',
'gantt',
'pie'
].forEach(function (type) {
if (seriesTypes[type]) {
seriesTypes[type].prototype.keyboardMoveVertical = false;
}
});
seriesProto.highlightNextValidPoint = (seriesHighlightNextValidPoint);
}
}
SeriesKeyboardNavigation.compose = compose;
/**
* Get the point in a series that is closest (in pixel distance) to a
* reference point. Optionally supply weight factors for x and y directions.
* @private
*/
function getClosestPoint(point, series, xWeight, yWeight) {
var minDistance = Infinity, dPoint, minIx, distance, i = series.points.length;
var hasUndefinedPosition = function (point) { return (!(defined(point.plotX) && defined(point.plotY))); };
if (hasUndefinedPosition(point)) {
return;
}
while (i--) {
dPoint = series.points[i];
if (hasUndefinedPosition(dPoint)) {
continue;
}
distance = (point.plotX - dPoint.plotX) *
(point.plotX - dPoint.plotX) *
(xWeight || 1) +
(point.plotY - dPoint.plotY) *
(point.plotY - dPoint.plotY) *
(yWeight || 1);
if (distance < minDistance) {
minDistance = distance;
minIx = i;
}
}
return defined(minIx) ? series.points[minIx] : void 0;
}
/**
* Highlights a point (show tooltip, display hover state, focus element).
*
* @private
* @function Highcharts.Point#highlight
*
* @return {Highcharts.Point}
* This highlighted point.
*/
function pointHighlight(highlightVisually) {
if (highlightVisually === void 0) { highlightVisually = true; }
var chart = this.series.chart;
if (!this.isNull && highlightVisually) {
this.onMouseOver(); // Show the hover marker and tooltip
}
else {
if (chart.tooltip) {
chart.tooltip.hide(0);
}
// Do not call blur on the element, as it messes up the focus of the
// div element of the chart
}
scrollToPoint(this);
// We focus only after calling onMouseOver because the state change can
// change z-index and mess up the element.
if (this.graphic) {
chart.setFocusToElement(this.graphic);
if (!highlightVisually && chart.focusElement) {
chart.focusElement.removeFocusBorder();
}
}
chart.highlightedPoint = this;
return this;
}
/**
* Highlight first valid point in a series. Returns the point if
* successfully highlighted, otherwise false. If there is a highlighted
* point in the series, use that as starting point.
*
* @private
* @function Highcharts.Series#highlightNextValidPoint
*/
function seriesHighlightNextValidPoint() {
var curPoint = this.chart.highlightedPoint, start = (curPoint && curPoint.series) === this ?
getPointIndex(curPoint) :
0, points = this.points, len = points.length;
if (points && len) {
for (var i = start; i < len; ++i) {
if (!isSkipPoint(points[i])) {
return points[i].highlight();
}
}
for (var j = start; j >= 0; --j) {
if (!isSkipPoint(points[j])) {
return points[j].highlight();
}
}
}
return false;
}
})(SeriesKeyboardNavigation || (SeriesKeyboardNavigation = {}));
/* *
*
* Default Export
*
* */
export default SeriesKeyboardNavigation;