1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | (function(factory) {
|
11 | if (typeof module === 'object' && module.exports) {
|
12 | module.exports = factory;
|
13 | } else {
|
14 | factory(Highcharts);
|
15 | }
|
16 | }(function(Highcharts) {
|
17 | (function(H) {
|
18 | |
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | 'use strict';
|
27 |
|
28 | var win = H.win,
|
29 | doc = win.document,
|
30 | each = H.each,
|
31 | erase = H.erase,
|
32 | addEvent = H.addEvent,
|
33 | removeEvent = H.removeEvent,
|
34 | fireEvent = H.fireEvent,
|
35 | dateFormat = H.dateFormat,
|
36 | merge = H.merge,
|
37 |
|
38 | typeToSeriesMap = {
|
39 | 'default': ['series', 'data point', 'data points'],
|
40 | 'line': ['line', 'data point', 'data points'],
|
41 | 'spline': ['line', 'data point', 'data points'],
|
42 | 'area': ['line', 'data point', 'data points'],
|
43 | 'areaspline': ['line', 'data point', 'data points'],
|
44 | 'pie': ['pie', 'slice', 'slices'],
|
45 | 'column': ['column series', 'column', 'columns'],
|
46 | 'bar': ['bar series', 'bar', 'bars'],
|
47 | 'scatter': ['scatter series', 'data point', 'data points'],
|
48 | 'boxplot': ['boxplot series', 'box', 'boxes'],
|
49 | 'arearange': ['arearange series', 'data point', 'data points'],
|
50 | 'areasplinerange': ['areasplinerange series', 'data point', 'data points'],
|
51 | 'bubble': ['bubble series', 'bubble', 'bubbles'],
|
52 | 'columnrange': ['columnrange series', 'column', 'columns'],
|
53 | 'errorbar': ['errorbar series', 'errorbar', 'errorbars'],
|
54 | 'funnel': ['funnel', 'data point', 'data points'],
|
55 | 'pyramid': ['pyramid', 'data point', 'data points'],
|
56 | 'waterfall': ['waterfall series', 'column', 'columns'],
|
57 | 'map': ['map', 'area', 'areas'],
|
58 | 'mapline': ['line', 'data point', 'data points'],
|
59 | 'mappoint': ['point series', 'data point', 'data points'],
|
60 | 'mapbubble': ['bubble series', 'bubble', 'bubbles']
|
61 | },
|
62 |
|
63 | typeDescriptionMap = {
|
64 | boxplot: ' Box plot charts are typically used to display groups of statistical data. ' +
|
65 | 'Each data point in the chart can have up to 5 values: minimum, lower quartile, median, upper quartile and maximum. ',
|
66 | arearange: ' Arearange charts are line charts displaying a range between a lower and higher value for each point. ',
|
67 | areasplinerange: ' These charts are line charts displaying a range between a lower and higher value for each point. ',
|
68 | bubble: ' Bubble charts are scatter charts where each data point also has a size value. ',
|
69 | columnrange: ' Columnrange charts are column charts displaying a range between a lower and higher value for each point. ',
|
70 | errorbar: ' Errorbar series are used to display the variability of the data. ',
|
71 | funnel: ' Funnel charts are used to display reduction of data in stages. ',
|
72 | pyramid: ' Pyramid charts consist of a single pyramid with item heights corresponding to each point value. ',
|
73 | waterfall: ' A waterfall chart is a column chart where each column contributes towards a total end value. '
|
74 | },
|
75 | commonKeys = ['name', 'id', 'category', 'x', 'value', 'y'],
|
76 | specialKeys = ['z', 'open', 'high', 'q3', 'median', 'q1', 'low', 'close'];
|
77 |
|
78 |
|
79 | H.setOptions({
|
80 | accessibility: {
|
81 | enabled: true,
|
82 | pointDescriptionThreshold: 30,
|
83 | keyboardNavigation: {
|
84 | enabled: true
|
85 |
|
86 | }
|
87 |
|
88 | }
|
89 | });
|
90 |
|
91 |
|
92 | function reverseChildNodes(node) {
|
93 | var i = node.childNodes.length;
|
94 | while (i--) {
|
95 | node.appendChild(node.childNodes[i]);
|
96 | }
|
97 | }
|
98 |
|
99 |
|
100 | function fakeClickEvent(element) {
|
101 | var fakeEvent;
|
102 | if (element && element.onclick) {
|
103 | fakeEvent = doc.createEvent('Events');
|
104 | fakeEvent.initEvent('click', true, false);
|
105 | element.onclick(fakeEvent);
|
106 | }
|
107 | }
|
108 |
|
109 |
|
110 | H.wrap(H.Series.prototype, 'render', function(proceed) {
|
111 | proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
112 | if (this.chart.options.accessibility.enabled) {
|
113 | this.setA11yDescription();
|
114 | }
|
115 | });
|
116 |
|
117 |
|
118 | H.Series.prototype.setA11yDescription = function() {
|
119 | var a11yOptions = this.chart.options.accessibility,
|
120 | firstPointEl = this.points && this.points[0].graphic && this.points[0].graphic.element,
|
121 | seriesEl = firstPointEl && firstPointEl.parentNode || this.graph && this.graph.element || this.group && this.group.element;
|
122 |
|
123 | if (seriesEl) {
|
124 |
|
125 |
|
126 | if (seriesEl.lastChild === firstPointEl) {
|
127 | reverseChildNodes(seriesEl);
|
128 | }
|
129 |
|
130 | if (this.points && (this.points.length < a11yOptions.pointDescriptionThreshold || a11yOptions.pointDescriptionThreshold === false)) {
|
131 | each(this.points, function(point) {
|
132 | if (point.graphic) {
|
133 | point.graphic.element.setAttribute('role', 'img');
|
134 | point.graphic.element.setAttribute('tabindex', '-1');
|
135 | point.graphic.element.setAttribute('aria-label', a11yOptions.pointDescriptionFormatter && a11yOptions.pointDescriptionFormatter(point) ||
|
136 | point.buildPointInfoString());
|
137 | }
|
138 | });
|
139 | }
|
140 |
|
141 | if (this.chart.series.length > 1 || a11yOptions.describeSingleSeries) {
|
142 | seriesEl.setAttribute('role', 'region');
|
143 | seriesEl.setAttribute('tabindex', '-1');
|
144 | seriesEl.setAttribute('aria-label', a11yOptions.seriesDescriptionFormatter && a11yOptions.seriesDescriptionFormatter(this) ||
|
145 | this.buildSeriesInfoString());
|
146 | }
|
147 | }
|
148 | };
|
149 |
|
150 |
|
151 | H.Series.prototype.buildSeriesInfoString = function() {
|
152 | var typeInfo = typeToSeriesMap[this.type] || typeToSeriesMap.default,
|
153 | description = this.description || this.options.description;
|
154 | return (this.name ? this.name + ', ' : '') +
|
155 | (this.chart.types.length === 1 ? typeInfo[0] : 'series') + ' ' + (this.index + 1) + ' of ' + (this.chart.series.length) +
|
156 | (this.chart.types.length === 1 ? ' with ' : '. ' + typeInfo[0] + ' with ') +
|
157 | (this.points.length + ' ' + (this.points.length === 1 ? typeInfo[1] : typeInfo[2])) +
|
158 | (description ? '. ' + description : '') +
|
159 | (this.chart.yAxis.length > 1 && this.yAxis ? '. Y axis, ' + this.yAxis.getDescription() : '') +
|
160 | (this.chart.xAxis.length > 1 && this.xAxis ? '. X axis, ' + this.xAxis.getDescription() : '');
|
161 | };
|
162 |
|
163 |
|
164 | H.Point.prototype.buildPointInfoString = function() {
|
165 | var point = this,
|
166 | series = point.series,
|
167 | a11yOptions = series.chart.options.accessibility,
|
168 | infoString = '',
|
169 | hasSpecialKey = false,
|
170 | dateTimePoint = series.xAxis && series.xAxis.isDatetimeAxis,
|
171 | timeDesc = dateTimePoint && dateFormat(a11yOptions.pointDateFormatter && a11yOptions.pointDateFormatter(point) || a11yOptions.pointDateFormat ||
|
172 | H.Tooltip.prototype.getXDateFormat(point, series.chart.options.tooltip, series.xAxis), point.x);
|
173 |
|
174 | each(specialKeys, function(key) {
|
175 | if (point[key] !== undefined) {
|
176 | hasSpecialKey = true;
|
177 | }
|
178 | });
|
179 |
|
180 |
|
181 | if (hasSpecialKey) {
|
182 | if (dateTimePoint) {
|
183 | infoString = timeDesc;
|
184 | }
|
185 | each(commonKeys.concat(specialKeys), function(key) {
|
186 | if (point[key] !== undefined && !(dateTimePoint && key === 'x')) {
|
187 | infoString += (infoString ? '. ' : '') + key + ', ' + this[key];
|
188 | }
|
189 | });
|
190 | } else {
|
191 |
|
192 | infoString = (this.name || timeDesc || this.category || this.id || 'x, ' + this.x) + ', ' +
|
193 | (this.value !== undefined ? this.value : this.y);
|
194 | }
|
195 |
|
196 | return (this.index + 1) + '. ' + infoString + '.' + (this.description ? ' ' + this.description : '');
|
197 | };
|
198 |
|
199 |
|
200 | H.Axis.prototype.getDescription = function() {
|
201 | return this.userOptions && this.userOptions.description || this.axisTitle && this.axisTitle.textStr ||
|
202 | this.options.id || this.categories && 'categories' || 'values';
|
203 | };
|
204 |
|
205 |
|
206 | H.Axis.prototype.panStep = function(direction, granularity) {
|
207 | var gran = granularity || 3,
|
208 | extremes = this.getExtremes(),
|
209 | step = (extremes.max - extremes.min) / gran * direction,
|
210 | newMax = extremes.max + step,
|
211 | newMin = extremes.min + step,
|
212 | size = newMax - newMin;
|
213 | if (direction < 0 && newMin < extremes.dataMin) {
|
214 | newMin = extremes.dataMin;
|
215 | newMax = newMin + size;
|
216 | } else if (direction > 0 && newMax > extremes.dataMax) {
|
217 | newMax = extremes.dataMax;
|
218 | newMin = newMax - size;
|
219 | }
|
220 | this.setExtremes(newMin, newMax);
|
221 | };
|
222 |
|
223 |
|
224 | H.wrap(H.Series.prototype, 'init', function(proceed) {
|
225 | proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
226 | var chart = this.chart;
|
227 | if (chart.options.accessibility.enabled) {
|
228 | chart.types = chart.types || [];
|
229 |
|
230 |
|
231 | if (chart.types.indexOf(this.type) < 0) {
|
232 | chart.types.push(this.type);
|
233 | }
|
234 |
|
235 | addEvent(this, 'remove', function() {
|
236 | var removedSeries = this,
|
237 | hasType = false;
|
238 |
|
239 |
|
240 | each(chart.series, function(s) {
|
241 | if (s !== removedSeries && chart.types.indexOf(removedSeries.type) < 0) {
|
242 | hasType = true;
|
243 | }
|
244 | });
|
245 | if (!hasType) {
|
246 | erase(chart.types, removedSeries.type);
|
247 | }
|
248 | });
|
249 | }
|
250 | });
|
251 |
|
252 |
|
253 | H.Chart.prototype.getTypeDescription = function() {
|
254 | var firstType = this.types && this.types[0],
|
255 | mapTitle = this.series[0] && this.series[0].mapTitle;
|
256 | if (!firstType) {
|
257 | return 'Empty chart.';
|
258 | } else if (firstType === 'map') {
|
259 | return mapTitle ? 'Map of ' + mapTitle : 'Map of unspecified region.';
|
260 | } else if (this.types.length > 1) {
|
261 | return 'Combination chart.';
|
262 | } else if (['spline', 'area', 'areaspline'].indexOf(firstType) > -1) {
|
263 | return 'Line chart.';
|
264 | }
|
265 | return firstType + ' chart.' + (typeDescriptionMap[firstType] || '');
|
266 | };
|
267 |
|
268 |
|
269 | H.Chart.prototype.getAxesDescription = function() {
|
270 | var numXAxes = this.xAxis.length,
|
271 | numYAxes = this.yAxis.length,
|
272 | desc = {},
|
273 | i;
|
274 |
|
275 | if (numXAxes) {
|
276 | desc.xAxis = 'The chart has ' + numXAxes + (numXAxes > 1 ? ' X axes' : ' X axis') + ' displaying ';
|
277 | if (numXAxes < 2) {
|
278 | desc.xAxis += this.xAxis[0].getDescription() + '.';
|
279 | } else {
|
280 | for (i = 0; i < numXAxes - 1; ++i) {
|
281 | desc.xAxis += (i ? ', ' : '') + this.xAxis[i].getDescription();
|
282 | }
|
283 | desc.xAxis += ' and ' + this.xAxis[i].getDescription() + '.';
|
284 | }
|
285 | }
|
286 |
|
287 | if (numYAxes) {
|
288 | desc.yAxis = 'The chart has ' + numYAxes + (numYAxes > 1 ? ' Y axes' : ' Y axis') + ' displaying ';
|
289 | if (numYAxes < 2) {
|
290 | desc.yAxis += this.yAxis[0].getDescription() + '.';
|
291 | } else {
|
292 | for (i = 0; i < numYAxes - 1; ++i) {
|
293 | desc.yAxis += (i ? ', ' : '') + this.yAxis[i].getDescription();
|
294 | }
|
295 | desc.yAxis += ' and ' + this.yAxis[i].getDescription() + '.';
|
296 | }
|
297 | }
|
298 |
|
299 | return desc;
|
300 | };
|
301 |
|
302 |
|
303 | H.Chart.prototype.addAccessibleContextMenuAttribs = function() {
|
304 | var exportList = this.exportDivElements;
|
305 | if (exportList) {
|
306 |
|
307 |
|
308 | each(exportList, function(item) {
|
309 | if (item.tagName === 'DIV' &&
|
310 | !(item.children && item.children.length)) {
|
311 | item.setAttribute('role', 'menuitem');
|
312 | item.setAttribute('tabindex', -1);
|
313 | }
|
314 | });
|
315 |
|
316 | exportList[0].parentNode.setAttribute('role', 'menu');
|
317 | exportList[0].parentNode.setAttribute('aria-label', 'Chart export');
|
318 | }
|
319 | };
|
320 |
|
321 |
|
322 | H.Point.prototype.highlight = function() {
|
323 | var chart = this.series.chart;
|
324 | if (this.graphic && this.graphic.element.focus) {
|
325 | this.graphic.element.focus();
|
326 | }
|
327 | if (!this.isNull) {
|
328 | this.onMouseOver();
|
329 | chart.tooltip.refresh(chart.tooltip.shared ? [this] : this);
|
330 | } else {
|
331 | chart.tooltip.hide(0);
|
332 |
|
333 | }
|
334 | chart.highlightedPoint = this;
|
335 | return this;
|
336 | };
|
337 |
|
338 |
|
339 |
|
340 | H.Chart.prototype.highlightAdjacentPoint = function(next) {
|
341 | var series = this.series,
|
342 | curPoint = this.highlightedPoint,
|
343 | newSeries,
|
344 | newPoint;
|
345 |
|
346 |
|
347 | if (!series[0] || !series[0].points) {
|
348 | return false;
|
349 | }
|
350 |
|
351 |
|
352 | if (!curPoint) {
|
353 | return series[0].points[0].highlight();
|
354 | }
|
355 |
|
356 | newSeries = series[curPoint.series.index + (next ? 1 : -1)];
|
357 | newPoint = next ?
|
358 |
|
359 | curPoint.series.points[curPoint.index + 1] || newSeries && newSeries.points[0] :
|
360 |
|
361 | curPoint.series.points[curPoint.index - 1] ||
|
362 | newSeries && newSeries.points[newSeries.points.length - 1];
|
363 |
|
364 |
|
365 | if (newPoint === undefined) {
|
366 | return false;
|
367 | }
|
368 |
|
369 |
|
370 | if (newPoint.isNull && this.options.accessibility.keyboardNavigation &&
|
371 | this.options.accessibility.keyboardNavigation.skipNullPoints) {
|
372 | this.highlightedPoint = newPoint;
|
373 | return this.highlightAdjacentPoint(next);
|
374 | }
|
375 |
|
376 |
|
377 | return newPoint.highlight();
|
378 | };
|
379 |
|
380 |
|
381 | H.Chart.prototype.showExportMenu = function() {
|
382 | if (this.exportSVGElements && this.exportSVGElements[0]) {
|
383 | this.exportSVGElements[0].element.onclick();
|
384 | this.highlightExportItem(0);
|
385 | }
|
386 | };
|
387 |
|
388 |
|
389 | H.Chart.prototype.highlightExportItem = function(ix) {
|
390 | var listItem = this.exportDivElements && this.exportDivElements[ix],
|
391 | curHighlighted = this.exportDivElements && this.exportDivElements[this.highlightedExportItem];
|
392 |
|
393 | if (listItem && listItem.tagName === 'DIV' && !(listItem.children && listItem.children.length)) {
|
394 | if (listItem.focus) {
|
395 | listItem.focus();
|
396 | }
|
397 | if (curHighlighted && curHighlighted.onmouseout) {
|
398 | curHighlighted.onmouseout();
|
399 | }
|
400 | if (listItem.onmouseover) {
|
401 | listItem.onmouseover();
|
402 | }
|
403 | this.highlightedExportItem = ix;
|
404 | return true;
|
405 | }
|
406 | };
|
407 |
|
408 |
|
409 | H.Chart.prototype.highlightRangeSelectorButton = function(ix) {
|
410 | var buttons = this.rangeSelector.buttons;
|
411 |
|
412 | if (buttons[this.highlightedRangeSelectorItemIx]) {
|
413 | buttons[this.highlightedRangeSelectorItemIx].setState(this.oldRangeSelectorItemState || 0);
|
414 | }
|
415 |
|
416 | this.highlightedRangeSelectorItemIx = ix;
|
417 | if (buttons[ix]) {
|
418 | if (buttons[ix].element.focus) {
|
419 | buttons[ix].element.focus();
|
420 | }
|
421 | this.oldRangeSelectorItemState = buttons[ix].state;
|
422 | buttons[ix].setState(2);
|
423 | return true;
|
424 | }
|
425 | return false;
|
426 | };
|
427 |
|
428 |
|
429 | H.Chart.prototype.hideExportMenu = function() {
|
430 | var exportList = this.exportDivElements;
|
431 | if (exportList) {
|
432 | each(exportList, function(el) {
|
433 | fireEvent(el, 'mouseleave');
|
434 | });
|
435 | if (exportList[this.highlightedExportItem] && exportList[this.highlightedExportItem].onmouseout) {
|
436 | exportList[this.highlightedExportItem].onmouseout();
|
437 | }
|
438 | this.highlightedExportItem = 0;
|
439 | this.renderTo.focus();
|
440 | }
|
441 | };
|
442 |
|
443 |
|
444 | H.Chart.prototype.addKeyboardNavEvents = function() {
|
445 | var chart = this;
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 | function KeyboardNavigationModule(options) {
|
453 | this.keyCodeMap = options.keyCodeMap;
|
454 | this.move = options.move;
|
455 | this.validate = options.validate;
|
456 | this.init = options.init;
|
457 | this.transformTabs = options.transformTabs !== false;
|
458 | }
|
459 | KeyboardNavigationModule.prototype = {
|
460 |
|
461 | run: function(e) {
|
462 | var navModule = this,
|
463 | keyCode = e.which || e.keyCode,
|
464 | handled = false;
|
465 | keyCode = this.transformTabs && keyCode === 9 ? (e.shiftKey ? 37 : 39) : keyCode;
|
466 | each(this.keyCodeMap, function(codeSet) {
|
467 | if (codeSet[0].indexOf(keyCode) > -1) {
|
468 | handled = codeSet[1].call(navModule, keyCode, e) === false ? false : true;
|
469 | }
|
470 | });
|
471 | return handled;
|
472 | }
|
473 | };
|
474 |
|
475 |
|
476 | function navModuleFactory(keyMap, options) {
|
477 | return new KeyboardNavigationModule(merge({
|
478 | keyCodeMap: keyMap,
|
479 |
|
480 |
|
481 | move: function(direction) {
|
482 | chart.keyboardNavigationModuleIndex += direction;
|
483 | var newModule = chart.keyboardNavigationModules[chart.keyboardNavigationModuleIndex];
|
484 | if (newModule) {
|
485 | if (newModule.validate && !newModule.validate()) {
|
486 | return this.move(direction);
|
487 | }
|
488 | if (newModule.init) {
|
489 | newModule.init(direction);
|
490 | return true;
|
491 | }
|
492 | }
|
493 |
|
494 | chart.keyboardNavigationModuleIndex = 0;
|
495 | chart.slipNextTab = true;
|
496 | return false;
|
497 | }
|
498 | }, options));
|
499 | }
|
500 |
|
501 |
|
502 | function keydownHandler(ev) {
|
503 | var e = ev || win.event,
|
504 | keyCode = e.which || e.keyCode,
|
505 | curNavModule = chart.keyboardNavigationModules[chart.keyboardNavigationModuleIndex];
|
506 |
|
507 |
|
508 | if (keyCode === 9) {
|
509 |
|
510 | if (chart.slipNextTab) {
|
511 | chart.slipNextTab = false;
|
512 | return;
|
513 | }
|
514 | }
|
515 |
|
516 | chart.slipNextTab = false;
|
517 |
|
518 |
|
519 | if (curNavModule) {
|
520 | if (curNavModule.run(e)) {
|
521 | e.preventDefault();
|
522 | }
|
523 | }
|
524 | }
|
525 |
|
526 |
|
527 |
|
528 |
|
529 | chart.keyboardNavigationModules = [
|
530 |
|
531 | navModuleFactory([
|
532 |
|
533 | [
|
534 | [37, 39],
|
535 | function(keyCode) {
|
536 | if (!chart.highlightAdjacentPoint(keyCode === 39)) {
|
537 | return this.move(keyCode === 39 ? 1 : -1);
|
538 | }
|
539 | }
|
540 | ],
|
541 |
|
542 | [
|
543 | [38, 40],
|
544 | function(keyCode) {
|
545 | var newSeries;
|
546 | if (chart.highlightedPoint) {
|
547 | newSeries = chart.series[chart.highlightedPoint.series.index + (keyCode === 38 ? -1 : 1)];
|
548 | if (newSeries && newSeries.points[0]) {
|
549 | newSeries.points[0].highlight();
|
550 | } else {
|
551 | return this.move(keyCode === 40 ? 1 : -1);
|
552 | }
|
553 | }
|
554 | }
|
555 | ],
|
556 |
|
557 | [
|
558 | [13, 32],
|
559 | function() {
|
560 | if (chart.highlightedPoint) {
|
561 | chart.highlightedPoint.firePointEvent('click');
|
562 | }
|
563 | }
|
564 | ]
|
565 | ], {
|
566 |
|
567 | init: function(direction) {
|
568 | var lastSeries = chart.series && chart.series[chart.series.length - 1],
|
569 | lastPoint = lastSeries && lastSeries.points && lastSeries.points[lastSeries.points.length - 1];
|
570 | if (direction < 0 && lastPoint) {
|
571 | lastPoint.highlight();
|
572 | }
|
573 | }
|
574 | }),
|
575 |
|
576 |
|
577 | navModuleFactory([
|
578 |
|
579 | [
|
580 | [37, 38],
|
581 | function() {
|
582 | var i = chart.highlightedExportItem || 0,
|
583 | reachedEnd = true,
|
584 | series = chart.series,
|
585 | newSeries;
|
586 |
|
587 | while (i--) {
|
588 | if (chart.highlightExportItem(i)) {
|
589 | reachedEnd = false;
|
590 | break;
|
591 | }
|
592 | }
|
593 | if (reachedEnd) {
|
594 | chart.hideExportMenu();
|
595 |
|
596 | if (series && series.length) {
|
597 | newSeries = series[series.length - 1];
|
598 | if (newSeries.points.length) {
|
599 | newSeries.points[newSeries.points.length - 1].highlight();
|
600 | }
|
601 | }
|
602 |
|
603 | return this.move(-1);
|
604 | }
|
605 | }
|
606 | ],
|
607 |
|
608 | [
|
609 | [39, 40],
|
610 | function() {
|
611 | var highlightedExportItem = chart.highlightedExportItem || 0,
|
612 | reachedEnd = true;
|
613 |
|
614 | for (var i = highlightedExportItem + 1; i < chart.exportDivElements.length; ++i) {
|
615 | if (chart.highlightExportItem(i)) {
|
616 | reachedEnd = false;
|
617 | break;
|
618 | }
|
619 | }
|
620 | if (reachedEnd) {
|
621 | chart.hideExportMenu();
|
622 | return this.move(1);
|
623 | }
|
624 | }
|
625 | ],
|
626 |
|
627 | [
|
628 | [13, 32],
|
629 | function() {
|
630 | fakeClickEvent(chart.exportDivElements[chart.highlightedExportItem]);
|
631 | }
|
632 | ]
|
633 | ], {
|
634 |
|
635 | validate: function() {
|
636 | return chart.exportChart && !(chart.options.exporting && chart.options.exporting.enabled === false);
|
637 | },
|
638 |
|
639 | init: function(direction) {
|
640 | chart.highlightedPoint = null;
|
641 | chart.showExportMenu();
|
642 |
|
643 | if (direction < 0 && chart.exportDivElements) {
|
644 | for (var i = chart.exportDivElements.length; i > -1; --i) {
|
645 | if (chart.highlightExportItem(i)) {
|
646 | break;
|
647 | }
|
648 | }
|
649 | }
|
650 | }
|
651 | }),
|
652 |
|
653 |
|
654 | navModuleFactory([
|
655 |
|
656 | [
|
657 | [38, 40, 37, 39],
|
658 | function(keyCode) {
|
659 | chart[keyCode === 38 || keyCode === 40 ? 'yAxis' : 'xAxis'][0].panStep(keyCode < 39 ? -1 : 1);
|
660 | }
|
661 | ],
|
662 |
|
663 |
|
664 | [
|
665 | [9],
|
666 | function(keyCode, e) {
|
667 | var button;
|
668 | chart.mapNavButtons[chart.focusedMapNavButtonIx].setState(0);
|
669 | if (e.shiftKey && !chart.focusedMapNavButtonIx || !e.shiftKey && chart.focusedMapNavButtonIx) {
|
670 | chart.mapZoom();
|
671 | return this.move(e.shiftKey ? -1 : 1);
|
672 | }
|
673 | chart.focusedMapNavButtonIx += e.shiftKey ? -1 : 1;
|
674 | button = chart.mapNavButtons[chart.focusedMapNavButtonIx];
|
675 | if (button.element.focus) {
|
676 | button.element.focus();
|
677 | }
|
678 | button.setState(2);
|
679 | }
|
680 | ],
|
681 |
|
682 |
|
683 | [
|
684 | [13, 32],
|
685 | function() {
|
686 | fakeClickEvent(chart.mapNavButtons[chart.focusedMapNavButtonIx].element);
|
687 | }
|
688 | ]
|
689 | ], {
|
690 |
|
691 | validate: function() {
|
692 | return chart.mapZoom && chart.mapNavButtons && chart.mapNavButtons.length === 2;
|
693 | },
|
694 |
|
695 |
|
696 | transformTabs: false,
|
697 |
|
698 |
|
699 | init: function(direction) {
|
700 | var zoomIn = chart.mapNavButtons[0],
|
701 | zoomOut = chart.mapNavButtons[1],
|
702 | initialButton = direction > 0 ? zoomIn : zoomOut;
|
703 |
|
704 | each(chart.mapNavButtons, function(button, i) {
|
705 | button.element.setAttribute('tabindex', -1);
|
706 | button.element.setAttribute('role', 'button');
|
707 | button.element.setAttribute('aria-label', 'Zoom ' + (i ? 'out' : '') + 'chart');
|
708 | });
|
709 |
|
710 | if (initialButton.element.focus) {
|
711 | initialButton.element.focus();
|
712 | }
|
713 | initialButton.setState(2);
|
714 | chart.focusedMapNavButtonIx = direction > 0 ? 0 : 1;
|
715 | }
|
716 | }),
|
717 |
|
718 |
|
719 | navModuleFactory([
|
720 |
|
721 | [
|
722 | [37, 39, 38, 40],
|
723 | function(keyCode) {
|
724 | var direction = (keyCode === 37 || keyCode === 38) ? -1 : 1;
|
725 |
|
726 | if (!chart.highlightRangeSelectorButton(chart.highlightedRangeSelectorItemIx + direction)) {
|
727 | return this.move(direction);
|
728 | }
|
729 | }
|
730 | ],
|
731 |
|
732 | [
|
733 | [13, 32],
|
734 | function() {
|
735 | if (chart.oldRangeSelectorItemState !== 3) {
|
736 | fakeClickEvent(chart.rangeSelector.buttons[chart.highlightedRangeSelectorItemIx].element);
|
737 | }
|
738 | }
|
739 | ]
|
740 | ], {
|
741 |
|
742 | validate: function() {
|
743 | return chart.rangeSelector && chart.rangeSelector.buttons && chart.rangeSelector.buttons.length;
|
744 | },
|
745 |
|
746 |
|
747 | init: function(direction) {
|
748 | each(chart.rangeSelector.buttons, function(button) {
|
749 | button.element.setAttribute('tabindex', '-1');
|
750 | button.element.setAttribute('role', 'button');
|
751 | button.element.setAttribute('aria-label', 'Select range ' + (button.text && button.text.textStr));
|
752 | });
|
753 |
|
754 | chart.highlightRangeSelectorButton(direction > 0 ? 0 : chart.rangeSelector.buttons.length - 1);
|
755 | }
|
756 | }),
|
757 |
|
758 |
|
759 | navModuleFactory([
|
760 |
|
761 | [
|
762 | [9, 38, 40],
|
763 | function(keyCode, e) {
|
764 | var direction = (keyCode === 9 && e.shiftKey || keyCode === 38) ? -1 : 1,
|
765 | newIx = chart.highlightedInputRangeIx = chart.highlightedInputRangeIx + direction;
|
766 |
|
767 | if (newIx > 1 || newIx < 0) {
|
768 | return this.move(direction);
|
769 | }
|
770 | chart.rangeSelector[newIx ? 'maxInput' : 'minInput'].focus();
|
771 | }
|
772 | ]
|
773 | ], {
|
774 |
|
775 | validate: function() {
|
776 | return chart.rangeSelector && chart.options.rangeSelector.inputEnabled !== false && chart.rangeSelector.minInput && chart.rangeSelector.maxInput;
|
777 | },
|
778 |
|
779 |
|
780 | transformTabs: false,
|
781 |
|
782 |
|
783 | init: function(direction) {
|
784 | each(['minInput', 'maxInput'], function(key, i) {
|
785 | if (chart.rangeSelector[key]) {
|
786 | chart.rangeSelector[key].setAttribute('tabindex', '-1');
|
787 | chart.rangeSelector[key].setAttribute('role', 'textbox');
|
788 | chart.rangeSelector[key].setAttribute('aria-label', 'Select ' + (i ? 'end' : 'start') + ' date.');
|
789 | }
|
790 | });
|
791 |
|
792 | chart.highlightedInputRangeIx = direction > 0 ? 0 : 1;
|
793 | chart.rangeSelector[chart.highlightedInputRangeIx ? 'maxInput' : 'minInput'].focus();
|
794 | }
|
795 | })
|
796 | ];
|
797 |
|
798 |
|
799 | chart.keyboardNavigationModuleIndex = 0;
|
800 |
|
801 |
|
802 | if (!chart.renderTo.tabIndex) {
|
803 | chart.renderTo.setAttribute('tabindex', '0');
|
804 | }
|
805 |
|
806 |
|
807 | addEvent(chart.renderTo, 'keydown', keydownHandler);
|
808 | addEvent(chart, 'destroy', function() {
|
809 | removeEvent(chart.renderTo, 'keydown', keydownHandler);
|
810 | });
|
811 | };
|
812 |
|
813 |
|
814 |
|
815 | H.Chart.prototype.addScreenReaderRegion = function(tableId) {
|
816 | var chart = this,
|
817 | series = chart.series,
|
818 | options = chart.options,
|
819 | a11yOptions = options.accessibility,
|
820 | hiddenSection = chart.screenReaderRegion = doc.createElement('div'),
|
821 | tableShortcut = doc.createElement('h3'),
|
822 | tableShortcutAnchor = doc.createElement('a'),
|
823 | chartHeading = doc.createElement('h3'),
|
824 | hiddenStyle = {
|
825 | position: 'absolute',
|
826 | left: '-9999px',
|
827 | top: 'auto',
|
828 | width: '1px',
|
829 | height: '1px',
|
830 | overflow: 'hidden'
|
831 | },
|
832 | chartTypes = chart.types || [],
|
833 |
|
834 | axesDesc = (chartTypes.length === 1 && chartTypes[0] === 'pie' || chartTypes[0] === 'map') && {} || chart.getAxesDescription(),
|
835 | chartTypeInfo = series[0] && typeToSeriesMap[series[0].type] || typeToSeriesMap.default;
|
836 |
|
837 | hiddenSection.setAttribute('role', 'region');
|
838 | hiddenSection.setAttribute('aria-label', 'Chart screen reader information.');
|
839 |
|
840 | hiddenSection.innerHTML = a11yOptions.screenReaderSectionFormatter && a11yOptions.screenReaderSectionFormatter(chart) ||
|
841 | '<div tabindex="0">Use regions/landmarks to skip ahead to chart' +
|
842 | (series.length > 1 ? ' and navigate between data series' : '') + '.</div><h3>Summary.</h3><div>' + (options.title.text || 'Chart') +
|
843 | (options.subtitle && options.subtitle.text ? '. ' + options.subtitle.text : '') +
|
844 | '</div><h3>Long description.</h3><div>' + (options.chart.description || 'No description available.') +
|
845 | '</div><h3>Structure.</h3><div>Chart type: ' + (options.chart.typeDescription || chart.getTypeDescription()) + '</div>' +
|
846 | (series.length === 1 ? '<div>' + chartTypeInfo[0] + ' with ' + series[0].points.length + ' ' +
|
847 | (series[0].points.length === 1 ? chartTypeInfo[1] : chartTypeInfo[2]) + '.</div>' : '') +
|
848 | (axesDesc.xAxis ? ('<div>' + axesDesc.xAxis + '</div>') : '') +
|
849 | (axesDesc.yAxis ? ('<div>' + axesDesc.yAxis + '</div>') : '');
|
850 |
|
851 |
|
852 | if (chart.getCSV) {
|
853 | tableShortcutAnchor.innerHTML = 'View as data table.';
|
854 | tableShortcutAnchor.href = '#' + tableId;
|
855 | tableShortcutAnchor.setAttribute('tabindex', '-1');
|
856 | tableShortcutAnchor.onclick = a11yOptions.onTableAnchorClick || function() {
|
857 | chart.viewData();
|
858 | doc.getElementById(tableId).focus();
|
859 | };
|
860 | tableShortcut.appendChild(tableShortcutAnchor);
|
861 |
|
862 | hiddenSection.appendChild(tableShortcut);
|
863 | }
|
864 |
|
865 | chartHeading.innerHTML = 'Chart graphic.';
|
866 | chart.renderTo.insertBefore(chartHeading, chart.renderTo.firstChild);
|
867 | chart.renderTo.insertBefore(hiddenSection, chart.renderTo.firstChild);
|
868 |
|
869 |
|
870 | merge(true, chartHeading.style, hiddenStyle);
|
871 | merge(true, hiddenSection.style, hiddenStyle);
|
872 | };
|
873 |
|
874 |
|
875 |
|
876 | H.Chart.prototype.callbacks.push(function(chart) {
|
877 | var options = chart.options,
|
878 | a11yOptions = options.accessibility;
|
879 |
|
880 | if (!a11yOptions.enabled) {
|
881 | return;
|
882 | }
|
883 |
|
884 | var titleElement = doc.createElementNS('http://www.w3.org/2000/svg', 'title'),
|
885 | exportGroupElement = doc.createElementNS('http://www.w3.org/2000/svg', 'g'),
|
886 | descElement = chart.container.getElementsByTagName('desc')[0],
|
887 | textElements = chart.container.getElementsByTagName('text'),
|
888 | titleId = 'highcharts-title-' + chart.index,
|
889 | tableId = 'highcharts-data-table-' + chart.index,
|
890 | chartTitle = options.title.text || 'Chart',
|
891 | oldColumnHeaderFormatter = options.exporting && options.exporting.csv && options.exporting.csv.columnHeaderFormatter,
|
892 | topLevelColumns = [];
|
893 |
|
894 |
|
895 | titleElement.textContent = chartTitle;
|
896 | titleElement.id = titleId;
|
897 | descElement.parentNode.insertBefore(titleElement, descElement);
|
898 | chart.renderTo.setAttribute('role', 'region');
|
899 | chart.renderTo.setAttribute('aria-label', chartTitle + '. Use up and down arrows to navigate.');
|
900 |
|
901 |
|
902 | if (chart.exportSVGElements && chart.exportSVGElements[0] && chart.exportSVGElements[0].element) {
|
903 | var oldExportCallback = chart.exportSVGElements[0].element.onclick,
|
904 | parent = chart.exportSVGElements[0].element.parentNode;
|
905 | chart.exportSVGElements[0].element.onclick = function() {
|
906 | oldExportCallback.apply(this, Array.prototype.slice.call(arguments));
|
907 | chart.addAccessibleContextMenuAttribs();
|
908 | chart.highlightExportItem(0);
|
909 | };
|
910 | chart.exportSVGElements[0].element.setAttribute('role', 'button');
|
911 | chart.exportSVGElements[0].element.setAttribute('aria-label', 'View export menu');
|
912 | exportGroupElement.appendChild(chart.exportSVGElements[0].element);
|
913 | exportGroupElement.setAttribute('role', 'region');
|
914 | exportGroupElement.setAttribute('aria-label', 'Chart export menu');
|
915 | parent.appendChild(exportGroupElement);
|
916 | }
|
917 |
|
918 |
|
919 | each(textElements, function(el) {
|
920 | el.setAttribute('aria-hidden', 'true');
|
921 | });
|
922 |
|
923 |
|
924 | chart.addScreenReaderRegion(tableId);
|
925 |
|
926 |
|
927 | if (a11yOptions.keyboardNavigation) {
|
928 | chart.addKeyboardNavEvents();
|
929 | }
|
930 |
|
931 |
|
932 |
|
933 |
|
934 | merge(true, options.exporting, {
|
935 | csv: {
|
936 | columnHeaderFormatter: function(series, key, keyLength) {
|
937 | var prevCol = topLevelColumns[topLevelColumns.length - 1];
|
938 | if (keyLength > 1) {
|
939 |
|
940 |
|
941 | if ((prevCol && prevCol.text) !== series.name) {
|
942 | topLevelColumns.push({
|
943 | text: series.name,
|
944 | span: keyLength
|
945 | });
|
946 | }
|
947 | }
|
948 | if (oldColumnHeaderFormatter) {
|
949 | return oldColumnHeaderFormatter.call(this, series, key, keyLength);
|
950 | }
|
951 | return keyLength > 1 ? key : series.name;
|
952 | }
|
953 | }
|
954 | });
|
955 |
|
956 |
|
957 | H.wrap(chart, 'getTable', function(proceed) {
|
958 | return proceed.apply(this, Array.prototype.slice.call(arguments, 1))
|
959 | .replace('<table>', '<table id="' + tableId + '" summary="Table representation of chart"><caption>' + chartTitle + '</caption>');
|
960 | });
|
961 |
|
962 |
|
963 | H.wrap(chart, 'viewData', function(proceed) {
|
964 | if (!this.insertedTable) {
|
965 | proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
966 |
|
967 | var table = doc.getElementById(tableId),
|
968 | body = table.getElementsByTagName('tbody')[0],
|
969 | firstRow = body.firstChild.children,
|
970 | columnHeaderRow = '<tr><td></td>',
|
971 | cell,
|
972 | newCell;
|
973 |
|
974 |
|
975 | table.setAttribute('tabindex', '-1');
|
976 |
|
977 |
|
978 | each(body.children, function(el) {
|
979 | cell = el.firstChild;
|
980 | newCell = doc.createElement('th');
|
981 | newCell.setAttribute('scope', 'row');
|
982 | newCell.innerHTML = cell.innerHTML;
|
983 | cell.parentNode.replaceChild(newCell, cell);
|
984 | });
|
985 |
|
986 |
|
987 | each(firstRow, function(el) {
|
988 | if (el.tagName === 'TH') {
|
989 | el.setAttribute('scope', 'col');
|
990 | }
|
991 | });
|
992 |
|
993 |
|
994 | if (topLevelColumns.length) {
|
995 | each(topLevelColumns, function(col) {
|
996 | columnHeaderRow += '<th scope="col" colspan="' + col.span + '">' + col.text + '</th>';
|
997 | });
|
998 | body.insertAdjacentHTML('afterbegin', columnHeaderRow);
|
999 | }
|
1000 | }
|
1001 | });
|
1002 | });
|
1003 |
|
1004 | }(Highcharts));
|
1005 | }));
|