334 lines
8.5 KiB
JavaScript
334 lines
8.5 KiB
JavaScript
/* *
|
|
*
|
|
* (c) 2009-2019 Øystein Moseng
|
|
*
|
|
* Accessibility component for chart zoom.
|
|
*
|
|
* License: www.highcharts.com/license
|
|
*
|
|
* */
|
|
|
|
'use strict';
|
|
|
|
import H from '../../../parts/Globals.js';
|
|
import AccessibilityComponent from '../AccessibilityComponent.js';
|
|
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
|
|
|
|
|
|
/**
|
|
* Pan along axis in a direction (1 or -1), optionally with a defined
|
|
* granularity (number of steps it takes to walk across current view)
|
|
*
|
|
* @private
|
|
* @function Highcharts.Axis#panStep
|
|
*
|
|
* @param {number} direction
|
|
* @param {number} [granularity]
|
|
*/
|
|
H.Axis.prototype.panStep = function (direction, granularity) {
|
|
var gran = granularity || 3,
|
|
extremes = this.getExtremes(),
|
|
step = (extremes.max - extremes.min) / gran * direction,
|
|
newMax = extremes.max + step,
|
|
newMin = extremes.min + step,
|
|
size = newMax - newMin;
|
|
|
|
if (direction < 0 && newMin < extremes.dataMin) {
|
|
newMin = extremes.dataMin;
|
|
newMax = newMin + size;
|
|
} else if (direction > 0 && newMax > extremes.dataMax) {
|
|
newMax = extremes.dataMax;
|
|
newMin = newMax - size;
|
|
}
|
|
this.setExtremes(newMin, newMax);
|
|
};
|
|
|
|
|
|
/**
|
|
* The ZoomComponent class
|
|
*
|
|
* @private
|
|
* @class
|
|
* @name Highcharts.ZoomComponent
|
|
* @param {Highcharts.Chart} chart
|
|
* Chart object
|
|
*/
|
|
var ZoomComponent = function (chart) {
|
|
this.initBase(chart);
|
|
this.init();
|
|
};
|
|
ZoomComponent.prototype = new AccessibilityComponent();
|
|
H.extend(ZoomComponent.prototype, /** @lends Highcharts.ZoomComponent */ {
|
|
|
|
/**
|
|
* Initialize the component
|
|
*/
|
|
init: function () {
|
|
var component = this,
|
|
chart = this.chart;
|
|
[
|
|
'afterShowResetZoom', 'afterDrilldown', 'drillupall'
|
|
].forEach(function (eventType) {
|
|
component.addEvent(chart, eventType, function () {
|
|
component.updateProxyOverlays();
|
|
});
|
|
});
|
|
},
|
|
|
|
|
|
/**
|
|
* Called when chart is updated
|
|
*/
|
|
onChartUpdate: function () {
|
|
var chart = this.chart,
|
|
component = this;
|
|
|
|
// Make map zoom buttons accessible
|
|
if (chart.mapNavButtons) {
|
|
chart.mapNavButtons.forEach(function (button, i) {
|
|
component.unhideElementFromScreenReaders(button.element);
|
|
button.element.setAttribute('tabindex', -1);
|
|
button.element.setAttribute('role', 'button');
|
|
button.element.setAttribute(
|
|
'aria-label',
|
|
chart.langFormat(
|
|
'accessibility.mapZoom' + (i ? 'Out' : 'In'),
|
|
{ chart: chart }
|
|
)
|
|
);
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Update the proxy overlays on every new render to ensure positions are
|
|
* correct.
|
|
*/
|
|
onChartRender: function () {
|
|
this.updateProxyOverlays();
|
|
},
|
|
|
|
|
|
/**
|
|
* Update proxy overlays, recreating the buttons.
|
|
*/
|
|
updateProxyOverlays: function () {
|
|
var component = this,
|
|
chart = this.chart,
|
|
proxyButton = function (buttonEl, buttonProp, groupProp, label) {
|
|
component.removeElement(component[groupProp]);
|
|
component[groupProp] = component.addProxyGroup();
|
|
component[buttonProp] = component.createProxyButton(
|
|
buttonEl,
|
|
component[groupProp],
|
|
{
|
|
'aria-label': label,
|
|
tabindex: -1
|
|
}
|
|
);
|
|
};
|
|
|
|
// Always start with a clean slate
|
|
component.removeElement(component.drillUpProxyGroup);
|
|
component.removeElement(component.resetZoomProxyGroup);
|
|
|
|
if (chart.resetZoomButton) {
|
|
proxyButton(
|
|
chart.resetZoomButton, 'resetZoomProxyButton',
|
|
'resetZoomProxyGroup', chart.langFormat(
|
|
'accessibility.resetZoomButton',
|
|
{ chart: chart }
|
|
)
|
|
);
|
|
}
|
|
|
|
if (chart.drillUpButton) {
|
|
proxyButton(
|
|
chart.drillUpButton, 'drillUpProxyButton',
|
|
'drillUpProxyGroup', chart.langFormat(
|
|
'accessibility.drillUpButton',
|
|
{
|
|
chart: chart,
|
|
buttonText: chart.getDrilldownBackText()
|
|
}
|
|
)
|
|
);
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Get keyboard navigation handler for map zoom.
|
|
* @private
|
|
* @return {Highcharts.KeyboardNavigationHandler} The module object
|
|
*/
|
|
getMapZoomNavigation: function () {
|
|
var keys = this.keyCodes,
|
|
chart = this.chart,
|
|
component = this;
|
|
|
|
return new KeyboardNavigationHandler(chart, {
|
|
keyCodeMap: [
|
|
// Arrow keys
|
|
[[
|
|
keys.up, keys.down, keys.left, keys.right
|
|
], function (keyCode) {
|
|
chart[
|
|
keyCode === keys.up || keyCode === keys.down ?
|
|
'yAxis' : 'xAxis'
|
|
][0].panStep(
|
|
keyCode === keys.left || keyCode === keys.up ? -1 : 1
|
|
);
|
|
return this.response.success;
|
|
}],
|
|
|
|
// Tabs
|
|
[[
|
|
keys.tab
|
|
], function (keyCode, e) {
|
|
var button;
|
|
|
|
// Deselect old
|
|
chart.mapNavButtons[
|
|
component.focusedMapNavButtonIx
|
|
].setState(0);
|
|
|
|
// Trying to go somewhere we can't?
|
|
if (
|
|
e.shiftKey && !component.focusedMapNavButtonIx ||
|
|
!e.shiftKey && component.focusedMapNavButtonIx
|
|
) {
|
|
chart.mapZoom(); // Reset zoom
|
|
// Nowhere to go, go to prev/next module
|
|
return this.response[e.shiftKey ? 'prev' : 'next'];
|
|
}
|
|
|
|
// Select other button
|
|
component.focusedMapNavButtonIx += e.shiftKey ? -1 : 1;
|
|
button = chart.mapNavButtons[
|
|
component.focusedMapNavButtonIx
|
|
];
|
|
chart.setFocusToElement(button.box, button.element);
|
|
button.setState(2);
|
|
|
|
return this.response.success;
|
|
}],
|
|
|
|
// Press button
|
|
[[
|
|
keys.space, keys.enter
|
|
], function () {
|
|
component.fakeClickEvent(
|
|
chart.mapNavButtons[
|
|
component.focusedMapNavButtonIx
|
|
].element
|
|
);
|
|
return this.response.success;
|
|
}]
|
|
],
|
|
|
|
// Only run this module if we have map zoom on the chart
|
|
validate: function () {
|
|
return (
|
|
chart.mapZoom &&
|
|
chart.mapNavButtons &&
|
|
chart.mapNavButtons.length === 2
|
|
);
|
|
},
|
|
|
|
// Make zoom buttons do their magic
|
|
init: function (direction) {
|
|
var zoomIn = chart.mapNavButtons[0],
|
|
zoomOut = chart.mapNavButtons[1],
|
|
initialButton = direction > 0 ? zoomIn : zoomOut;
|
|
chart.setFocusToElement(
|
|
initialButton.box, initialButton.element
|
|
);
|
|
initialButton.setState(2);
|
|
component.focusedMapNavButtonIx = direction > 0 ? 0 : 1;
|
|
}
|
|
});
|
|
},
|
|
|
|
|
|
/**
|
|
* Get keyboard navigation handler for a simple chart button. Provide the
|
|
* button reference for the chart, and a function to call on click.
|
|
*
|
|
* @private
|
|
* @param {string} buttonProp The property on chart referencing the button.
|
|
* @return {Highcharts.KeyboardNavigationHandler} The module object
|
|
*/
|
|
simpleButtonNavigation: function (buttonProp, proxyProp, onClick) {
|
|
var keys = this.keyCodes,
|
|
component = this,
|
|
chart = this.chart;
|
|
|
|
return new KeyboardNavigationHandler(chart, {
|
|
keyCodeMap: [
|
|
// Arrow/tab just move
|
|
[[
|
|
keys.tab, keys.up, keys.down, keys.left, keys.right
|
|
], function (keyCode, e) {
|
|
return this.response[
|
|
keyCode === this.tab && e.shiftKey ||
|
|
keyCode === keys.left || keyCode === keys.up ?
|
|
'prev' : 'next'
|
|
];
|
|
}],
|
|
|
|
// Select to click
|
|
[[
|
|
keys.space, keys.enter
|
|
], function () {
|
|
onClick(chart);
|
|
return this.response.success;
|
|
}]
|
|
],
|
|
|
|
// Only run if we have the button
|
|
validate: function () {
|
|
return chart[buttonProp] && chart[buttonProp].box &&
|
|
component[proxyProp];
|
|
},
|
|
|
|
// Focus button initially
|
|
init: function () {
|
|
chart.setFocusToElement(
|
|
chart[buttonProp].box, component[proxyProp]
|
|
);
|
|
}
|
|
});
|
|
},
|
|
|
|
|
|
/**
|
|
* Get keyboard navigation handlers for this component.
|
|
* @return {Array<Highcharts.KeyboardNavigationHandler>}
|
|
* List of module objects
|
|
*/
|
|
getKeyboardNavigation: function () {
|
|
return [
|
|
this.simpleButtonNavigation(
|
|
'resetZoomButton',
|
|
'resetZoomProxyButton',
|
|
function (chart) {
|
|
chart.zoomOut();
|
|
}
|
|
),
|
|
this.simpleButtonNavigation(
|
|
'drillUpButton',
|
|
'drillUpProxyButton',
|
|
function (chart) {
|
|
chart.drillUp();
|
|
}
|
|
),
|
|
this.getMapZoomNavigation()
|
|
];
|
|
}
|
|
|
|
});
|
|
|
|
export default ZoomComponent;
|