highcharts-rails 5.0.14 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +60 -0
  3. data/Rakefile +54 -5
  4. data/app/assets/images/highcharts/earth.svg +432 -0
  5. data/app/assets/javascripts/highcharts.js +5103 -3147
  6. data/app/assets/javascripts/highcharts/highcharts-3d.js +930 -277
  7. data/app/assets/javascripts/highcharts/highcharts-more.js +1374 -249
  8. data/app/assets/javascripts/highcharts/lib/canvg.js +3073 -0
  9. data/app/assets/javascripts/highcharts/lib/jspdf.js +16624 -0
  10. data/app/assets/javascripts/highcharts/lib/rgbcolor.js +299 -0
  11. data/app/assets/javascripts/highcharts/lib/svg2pdf.js +3488 -0
  12. data/app/assets/javascripts/highcharts/modules/accessibility.js +654 -212
  13. data/app/assets/javascripts/highcharts/modules/annotations.js +1552 -274
  14. data/app/assets/javascripts/highcharts/modules/boost-canvas.js +773 -0
  15. data/app/assets/javascripts/highcharts/modules/boost.js +636 -210
  16. data/app/assets/javascripts/highcharts/modules/broken-axis.js +2 -2
  17. data/app/assets/javascripts/highcharts/modules/bullet.js +364 -0
  18. data/app/assets/javascripts/highcharts/modules/data.js +766 -38
  19. data/app/assets/javascripts/highcharts/modules/drag-panes.js +588 -0
  20. data/app/assets/javascripts/highcharts/modules/drilldown.js +106 -36
  21. data/app/assets/javascripts/highcharts/modules/export-data.js +597 -0
  22. data/app/assets/javascripts/highcharts/modules/exporting.js +424 -162
  23. data/app/assets/javascripts/highcharts/modules/funnel.js +144 -22
  24. data/app/assets/javascripts/highcharts/modules/gantt.js +1154 -0
  25. data/app/assets/javascripts/highcharts/modules/grid-axis.js +1 -1
  26. data/app/assets/javascripts/highcharts/modules/heatmap.js +406 -80
  27. data/app/assets/javascripts/highcharts/modules/histogram-bellcurve.js +513 -0
  28. data/app/assets/javascripts/highcharts/modules/item-series.js +126 -0
  29. data/app/assets/javascripts/highcharts/modules/no-data-to-display.js +31 -13
  30. data/app/assets/javascripts/highcharts/modules/offline-exporting.js +179 -57
  31. data/app/assets/javascripts/highcharts/modules/oldie.js +1378 -0
  32. data/app/assets/javascripts/highcharts/modules/overlapping-datalabels.js +8 -6
  33. data/app/assets/javascripts/highcharts/modules/parallel-coordinates.js +494 -0
  34. data/app/assets/javascripts/highcharts/modules/pareto.js +275 -0
  35. data/app/assets/javascripts/highcharts/modules/sankey.js +641 -0
  36. data/app/assets/javascripts/highcharts/modules/series-label.js +355 -145
  37. data/app/assets/javascripts/highcharts/modules/solid-gauge.js +122 -1
  38. data/app/assets/javascripts/highcharts/modules/static-scale.js +64 -0
  39. data/app/assets/javascripts/highcharts/modules/stock.js +1944 -676
  40. data/app/assets/javascripts/highcharts/modules/streamgraph.js +139 -0
  41. data/app/assets/javascripts/highcharts/modules/sunburst.js +2403 -0
  42. data/app/assets/javascripts/highcharts/modules/tilemap.js +1199 -0
  43. data/app/assets/javascripts/highcharts/modules/treemap.js +538 -134
  44. data/app/assets/javascripts/highcharts/modules/variable-pie.js +490 -0
  45. data/app/assets/javascripts/highcharts/modules/variwide.js +283 -0
  46. data/app/assets/javascripts/highcharts/modules/vector.js +294 -0
  47. data/app/assets/javascripts/highcharts/modules/windbarb.js +490 -0
  48. data/app/assets/javascripts/highcharts/modules/wordcloud.js +681 -0
  49. data/app/assets/javascripts/highcharts/modules/xrange.js +615 -0
  50. data/app/assets/javascripts/highcharts/themes/avocado.js +54 -0
  51. data/app/assets/javascripts/highcharts/themes/dark-blue.js +6 -6
  52. data/app/assets/javascripts/highcharts/themes/dark-green.js +6 -6
  53. data/app/assets/javascripts/highcharts/themes/dark-unica.js +6 -6
  54. data/app/assets/javascripts/highcharts/themes/gray.js +14 -10
  55. data/app/assets/javascripts/highcharts/themes/grid-light.js +6 -6
  56. data/app/assets/javascripts/highcharts/themes/grid.js +7 -5
  57. data/app/assets/javascripts/highcharts/themes/sand-signika.js +8 -7
  58. data/app/assets/javascripts/highcharts/themes/skies.js +15 -9
  59. data/app/assets/javascripts/highcharts/themes/sunset.js +53 -0
  60. data/app/assets/stylesheets/highcharts/highcharts.css +802 -0
  61. data/app/assets/stylesheets/highcharts/highcharts.scss +665 -0
  62. data/lib/highcharts/version.rb +1 -1
  63. metadata +31 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Highcharts JS v5.0.14 (2017-07-28)
2
+ * @license Highcharts JS v6.0.0 (2017-10-04)
3
3
  * Accessibility module
4
4
  *
5
5
  * (c) 2010-2017 Highsoft AS
@@ -24,6 +24,7 @@
24
24
  *
25
25
  * License: www.highcharts.com/license
26
26
  */
27
+ /* eslint max-len: ["warn", 80, 4] */
27
28
 
28
29
  var win = H.win,
29
30
  doc = win.document,
@@ -34,7 +35,18 @@
34
35
  fireEvent = H.fireEvent,
35
36
  dateFormat = H.dateFormat,
36
37
  merge = H.merge,
37
- // Human readable description of series and each point in singular and plural
38
+ // CSS style to hide element from visual users while still exposing it to
39
+ // screen readers
40
+ hiddenStyle = {
41
+ position: 'absolute',
42
+ left: '-9999px',
43
+ top: 'auto',
44
+ width: '1px',
45
+ height: '1px',
46
+ overflow: 'hidden'
47
+ },
48
+ // Human readable description of series and each point in singular and
49
+ // plural
38
50
  typeToSeriesMap = {
39
51
  'default': ['series', 'data point', 'data points'],
40
52
  'line': ['line', 'data point', 'data points'],
@@ -47,7 +59,11 @@
47
59
  'scatter': ['scatter series', 'data point', 'data points'],
48
60
  'boxplot': ['boxplot series', 'box', 'boxes'],
49
61
  'arearange': ['arearange series', 'data point', 'data points'],
50
- 'areasplinerange': ['areasplinerange series', 'data point', 'data points'],
62
+ 'areasplinerange': [
63
+ 'areasplinerange series',
64
+ 'data point',
65
+ 'data points'
66
+ ],
51
67
  'bubble': ['bubble series', 'bubble', 'bubbles'],
52
68
  'columnrange': ['columnrange series', 'column', 'columns'],
53
69
  'errorbar': ['errorbar series', 'errorbar', 'errorbars'],
@@ -61,16 +77,26 @@
61
77
  },
62
78
  // Descriptions for exotic chart types
63
79
  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. '
80
+ boxplot: ' Box plot charts are typically used to display groups of ' +
81
+ 'statistical data. Each data point in the chart can have up to 5 ' +
82
+ 'values: minimum, lower quartile, median, upper quartile and ' +
83
+ 'maximum. ',
84
+ arearange: ' Arearange charts are line charts displaying a range ' +
85
+ 'between a lower and higher value for each point. ',
86
+ areasplinerange: ' These charts are line charts displaying a range ' +
87
+ 'between a lower and higher value for each point. ',
88
+ bubble: ' Bubble charts are scatter charts where each data point ' +
89
+ 'also has a size value. ',
90
+ columnrange: ' Columnrange charts are column charts displaying a ' +
91
+ 'range between a lower and higher value for each point. ',
92
+ errorbar: ' Errorbar series are used to display the variability of ' +
93
+ 'the data. ',
94
+ funnel: ' Funnel charts are used to display reduction of data in ' +
95
+ 'stages. ',
96
+ pyramid: ' Pyramid charts consist of a single pyramid with item ' +
97
+ 'heights corresponding to each point value. ',
98
+ waterfall: ' A waterfall chart is a column chart where each column ' +
99
+ 'contributes towards a total end value. '
74
100
  };
75
101
 
76
102
  // If a point has one of the special keys defined, we expose all keys to the
@@ -85,6 +111,15 @@
85
111
  H.seriesTypes.pie.prototype.specialKeys = [];
86
112
  }
87
113
 
114
+ // Set for which series types it makes sense to move to the closest point with
115
+ // up/down arrows, and which series types should just move to next series.
116
+ H.Series.prototype.keyboardMoveVertical = true;
117
+ each(['column', 'pie'], function(type) {
118
+ if (H.seriesTypes[type]) {
119
+ H.seriesTypes[type].prototype.keyboardMoveVertical = false;
120
+ }
121
+ });
122
+
88
123
 
89
124
  /**
90
125
  * Accessibility options
@@ -101,7 +136,6 @@
101
136
  * com/docs/chart-concepts/accessibility).
102
137
  *
103
138
  * @since 5.0.0
104
- * @product highcharts highstock highmaps
105
139
  */
106
140
  accessibility: {
107
141
 
@@ -111,7 +145,6 @@
111
145
  * @type {Boolean}
112
146
  * @default true
113
147
  * @since 5.0.0
114
- * @product highcharts highstock highmaps
115
148
  */
116
149
  enabled: true,
117
150
 
@@ -124,7 +157,6 @@
124
157
  * @type {Number|Boolean}
125
158
  * @default 30
126
159
  * @since 5.0.0
127
- * @product highcharts highstock highmaps
128
160
  */
129
161
  pointDescriptionThreshold: 30, // set to false to disable
130
162
 
@@ -133,7 +165,6 @@
133
165
  *
134
166
  * @type {Object}
135
167
  * @since 5.0.0
136
- * @product highcharts highstock highmaps
137
168
  */
138
169
  keyboardNavigation: {
139
170
 
@@ -144,17 +175,7 @@
144
175
  * @default true
145
176
  * @since 5.0.0
146
177
  */
147
- enabled: true,
148
-
149
- /**
150
- * Enable tab navigation for points. Without this, only arrow keys
151
- * can be used to navigate between points.
152
- *
153
- * @type {Boolean}
154
- * @default {all} true
155
- * @since next
156
- */
157
- tabThroughPoints: true
178
+ enabled: true
158
179
 
159
180
  /**
160
181
  * Skip null points when navigating through points with the
@@ -174,7 +195,6 @@
174
195
  * @type {Boolean}
175
196
  * @default false
176
197
  * @since 5.0.0
177
- * @product highcharts highstock highmaps
178
198
  * @apioption accessibility.describeSingleSeries
179
199
  */
180
200
 
@@ -196,8 +216,8 @@
196
216
  *
197
217
  * Defaults to the same format as in tooltip.
198
218
  *
199
- * For an overview of the replacement codes, see [dateFormat](
200
- * #Highcharts.dateFormat).
219
+ * For an overview of the replacement codes, see
220
+ * [dateFormat](#Highcharts.dateFormat).
201
221
  *
202
222
  * @type {String}
203
223
  * @see [pointDateFormatter](#accessibility.pointDateFormatter)
@@ -206,11 +226,11 @@
206
226
  */
207
227
 
208
228
  /**
209
- * Formatter function to determine the date/time format used with points
210
- * on datetime axes when describing them to screen reader users. Receives
211
- * one argument, `point`, referring to the point to describe. Should
212
- * return a date format string compatible with [dateFormat](#Highcharts.
213
- * dateFormat).
229
+ * Formatter function to determine the date/time format used with
230
+ * points on datetime axes when describing them to screen reader users.
231
+ * Receives one argument, `point`, referring to the point to describe.
232
+ * Should return a date format string compatible with
233
+ * [dateFormat](#Highcharts.dateFormat).
214
234
  *
215
235
  * @type {Function}
216
236
  * @see [pointDateFormat](#accessibility.pointDateFormat)
@@ -219,13 +239,14 @@
219
239
  */
220
240
 
221
241
  /**
222
- * Formatter function to use instead of the default for point descriptions.
242
+ * Formatter function to use instead of the default for point
243
+ * descriptions.
223
244
  * Receives one argument, `point`, referring to the point to describe.
224
245
  * Should return a String with the description of the point for a screen
225
246
  * reader user.
226
247
  *
227
248
  * @type {Function}
228
- * @see [point.description](#series<line>.data.description)
249
+ * @see [point.description](#series.line.data.description)
229
250
  * @since 5.0.0
230
251
  * @apioption accessibility.pointDescriptionFormatter
231
252
  */
@@ -236,8 +257,8 @@
236
257
  * to the chart object. Should return a String with the HTML content
237
258
  * of the region.
238
259
  *
239
- * The link to view the chart as a data table will be added automatically
240
- * after the custom HTML content.
260
+ * The link to view the chart as a data table will be added
261
+ * automatically after the custom HTML content.
241
262
  *
242
263
  * @type {Function}
243
264
  * @default undefined
@@ -246,10 +267,10 @@
246
267
  */
247
268
 
248
269
  /**
249
- * Formatter function to use instead of the default for series descriptions.
250
- * Receives one argument, `series`, referring to the series to describe.
251
- * Should return a String with the description of the series for a
252
- * screen reader user.
270
+ * Formatter function to use instead of the default for series
271
+ * descriptions. Receives one argument, `series`, referring to the
272
+ * series to describe. Should return a String with the description of
273
+ * the series for a screen reader user.
253
274
  *
254
275
  * @type {Function}
255
276
  * @see [series.description](#plotOptions.series.description)
@@ -290,6 +311,24 @@
290
311
  * @apioption chart.typeDescription
291
312
  */
292
313
 
314
+ /**
315
+ * Keyboard navigation for the legend. Requires the Accessibility module.
316
+ * @since 5.0.14
317
+ * @apioption legend.keyboardNavigation
318
+ */
319
+
320
+ /**
321
+ * Enable/disable keyboard navigation for the legend. Requires the Accessibility
322
+ * module.
323
+ *
324
+ * @type {Boolean}
325
+ * @see [accessibility.keyboardNavigation](#accessibility.keyboardNavigation.
326
+ * enabled)
327
+ * @default true
328
+ * @since 5.0.13
329
+ * @apioption legend.keyboardNavigation.enabled
330
+ */
331
+
293
332
  /**
294
333
  * HTML encode some characters vulnerable for XSS.
295
334
  * @param {string} html The input string
@@ -323,6 +362,43 @@
323
362
  }
324
363
  }
325
364
 
365
+ // Determine if a point should be skipped
366
+ function isSkipPoint(point) {
367
+ return point.isNull &&
368
+ point.series.chart.options.accessibility
369
+ .keyboardNavigation.skipNullPoints ||
370
+ point.series.options.skipKeyboardNavigation ||
371
+ !point.series.visible;
372
+ }
373
+
374
+ // Get the point in a series that is closest to a reference point
375
+ // Optionally supply weight factors for x and y directions
376
+ function getClosestPoint(point, series, xWeight, yWeight) {
377
+ var minDistance = Infinity,
378
+ dPoint,
379
+ minIx,
380
+ distance,
381
+ i = series.points.length;
382
+ if (point.plotX === undefined || point.plotY === undefined) {
383
+ return;
384
+ }
385
+ while (i--) {
386
+ dPoint = series.points[i];
387
+ if (dPoint.plotX === undefined || dPoint.plotY === undefined) {
388
+ return;
389
+ }
390
+ distance = (point.plotX - dPoint.plotX) *
391
+ (point.plotX - dPoint.plotX) * (xWeight || 1) +
392
+ (point.plotY - dPoint.plotY) *
393
+ (point.plotY - dPoint.plotY) * (yWeight || 1);
394
+ if (distance < minDistance) {
395
+ minDistance = distance;
396
+ minIx = i;
397
+ }
398
+ }
399
+ return series.points[minIx || 0];
400
+ }
401
+
326
402
  // Whenever drawing series, put info on DOM elements
327
403
  H.wrap(H.Series.prototype, 'render', function(proceed) {
328
404
  proceed.apply(this, Array.prototype.slice.call(arguments, 1));
@@ -334,17 +410,35 @@
334
410
  // Put accessible info on series and points of a series
335
411
  H.Series.prototype.setA11yDescription = function() {
336
412
  var a11yOptions = this.chart.options.accessibility,
337
- firstPointEl = this.points && this.points.length && this.points[0].graphic && this.points[0].graphic.element,
338
- seriesEl = firstPointEl && firstPointEl.parentNode || this.graph && this.graph.element || this.group && this.group.element; // Could be tracker series depending on series type
413
+ firstPointEl = (
414
+ this.points &&
415
+ this.points.length &&
416
+ this.points[0].graphic &&
417
+ this.points[0].graphic.element
418
+ ),
419
+ seriesEl = (
420
+ firstPointEl &&
421
+ firstPointEl.parentNode || this.graph &&
422
+ this.graph.element || this.group &&
423
+ this.group.element
424
+ ); // Could be tracker series depending on series type
339
425
 
340
426
  if (seriesEl) {
341
- // For some series types the order of elements do not match the order of points in series
342
- // In that case we have to reverse them in order for AT to read them out in an understandable order
427
+ // For some series types the order of elements do not match the order of
428
+ // points in series. In that case we have to reverse them in order for
429
+ // AT to read them out in an understandable order
343
430
  if (seriesEl.lastChild === firstPointEl) {
344
431
  reverseChildNodes(seriesEl);
345
432
  }
346
- // Make individual point elements accessible if possible. Note: If markers are disabled there might not be any elements there to make accessible.
347
- if (this.points && (this.points.length < a11yOptions.pointDescriptionThreshold || a11yOptions.pointDescriptionThreshold === false)) {
433
+ // Make individual point elements accessible if possible. Note: If
434
+ // markers are disabled there might not be any elements there to make
435
+ // accessible.
436
+ if (
437
+ this.points && (
438
+ this.points.length < a11yOptions.pointDescriptionThreshold ||
439
+ a11yOptions.pointDescriptionThreshold === false
440
+ )
441
+ ) {
348
442
  each(this.points, function(point) {
349
443
  if (point.graphic) {
350
444
  point.graphic.element.setAttribute('role', 'img');
@@ -365,23 +459,46 @@
365
459
  this.options.exposeElementToA11y ? 'img' : 'region'
366
460
  );
367
461
  seriesEl.setAttribute('tabindex', '-1');
368
- seriesEl.setAttribute('aria-label', a11yOptions.seriesDescriptionFormatter && a11yOptions.seriesDescriptionFormatter(this) ||
369
- this.buildSeriesInfoString());
462
+ seriesEl.setAttribute(
463
+ 'aria-label',
464
+ a11yOptions.seriesDescriptionFormatter &&
465
+ a11yOptions.seriesDescriptionFormatter(this) ||
466
+ this.buildSeriesInfoString()
467
+ );
370
468
  }
371
469
  }
372
470
  };
373
471
 
374
472
  // Return string with information about series
375
473
  H.Series.prototype.buildSeriesInfoString = function() {
376
- var typeInfo = typeToSeriesMap[this.type] || typeToSeriesMap['default'], // eslint-disable-line dot-notation
474
+ var typeInfo = (
475
+ typeToSeriesMap[this.type] ||
476
+ typeToSeriesMap['default'] // eslint-disable-line dot-notation
477
+ ),
377
478
  description = this.description || this.options.description;
378
479
  return (this.name ? this.name + ', ' : '') +
379
- (this.chart.types.length === 1 ? typeInfo[0] : 'series') + ' ' + (this.index + 1) + ' of ' + (this.chart.series.length) +
380
- (this.chart.types.length === 1 ? ' with ' : '. ' + typeInfo[0] + ' with ') +
381
- (this.points.length + ' ' + (this.points.length === 1 ? typeInfo[1] : typeInfo[2])) +
480
+ (this.chart.types.length === 1 ? typeInfo[0] : 'series') +
481
+ ' ' + (this.index + 1) + ' of ' + (this.chart.series.length) +
482
+ (
483
+ this.chart.types.length === 1 ?
484
+ ' with ' :
485
+ '. ' + typeInfo[0] + ' with '
486
+ ) +
487
+ (
488
+ this.points.length + ' ' +
489
+ (this.points.length === 1 ? typeInfo[1] : typeInfo[2])
490
+ ) +
382
491
  (description ? '. ' + description : '') +
383
- (this.chart.yAxis.length > 1 && this.yAxis ? '. Y axis, ' + this.yAxis.getDescription() : '') +
384
- (this.chart.xAxis.length > 1 && this.xAxis ? '. X axis, ' + this.xAxis.getDescription() : '');
492
+ (
493
+ this.chart.yAxis.length > 1 && this.yAxis ?
494
+ '. Y axis, ' + this.yAxis.getDescription() :
495
+ ''
496
+ ) +
497
+ (
498
+ this.chart.xAxis.length > 1 && this.xAxis ?
499
+ '. X axis, ' + this.xAxis.getDescription() :
500
+ ''
501
+ );
385
502
  };
386
503
 
387
504
  // Return string with information about point
@@ -391,38 +508,66 @@
391
508
  a11yOptions = series.chart.options.accessibility,
392
509
  infoString = '',
393
510
  dateTimePoint = series.xAxis && series.xAxis.isDatetimeAxis,
394
- timeDesc = dateTimePoint && dateFormat(a11yOptions.pointDateFormatter && a11yOptions.pointDateFormatter(point) || a11yOptions.pointDateFormat ||
395
- H.Tooltip.prototype.getXDateFormat(point, series.chart.options.tooltip, series.xAxis), point.x),
511
+ timeDesc =
512
+ dateTimePoint &&
513
+ dateFormat(
514
+ a11yOptions.pointDateFormatter &&
515
+ a11yOptions.pointDateFormatter(point) ||
516
+ a11yOptions.pointDateFormat ||
517
+ H.Tooltip.prototype.getXDateFormat(
518
+ point,
519
+ series.chart.options.tooltip,
520
+ series.xAxis
521
+ ),
522
+ point.x
523
+ ),
396
524
  hasSpecialKey = H.find(series.specialKeys, function(key) {
397
525
  return point[key] !== undefined;
398
526
  });
399
527
 
400
- // If the point has one of the less common properties defined, display all that are defined
528
+ // If the point has one of the less common properties defined, display all
529
+ // that are defined
401
530
  if (hasSpecialKey) {
402
531
  if (dateTimePoint) {
403
532
  infoString = timeDesc;
404
533
  }
405
534
  each(series.commonKeys.concat(series.specialKeys), function(key) {
406
535
  if (point[key] !== undefined && !(dateTimePoint && key === 'x')) {
407
- infoString += (infoString ? '. ' : '') + key + ', ' + point[key];
536
+ infoString += (infoString ? '. ' : '') +
537
+ key + ', ' +
538
+ point[key];
408
539
  }
409
540
  });
410
541
  } else {
411
542
  // Pick and choose properties for a succint label
412
- infoString = (this.name || timeDesc || this.category || this.id || 'x, ' + this.x) + ', ' +
543
+ infoString =
544
+ (
545
+ this.name ||
546
+ timeDesc ||
547
+ this.category ||
548
+ this.id ||
549
+ 'x, ' + this.x
550
+ ) + ', ' +
413
551
  (this.value !== undefined ? this.value : this.y);
414
552
  }
415
553
 
416
- return (this.index + 1) + '. ' + infoString + '.' + (this.description ? ' ' + this.description : '');
554
+ return (this.index + 1) + '. ' + infoString + '.' +
555
+ (this.description ? ' ' + this.description : '');
417
556
  };
418
557
 
419
558
  // Get descriptive label for axis
420
559
  H.Axis.prototype.getDescription = function() {
421
- return this.userOptions && this.userOptions.description || this.axisTitle && this.axisTitle.textStr ||
422
- this.options.id || this.categories && 'categories' || 'values';
560
+ return (
561
+ this.userOptions && this.userOptions.description ||
562
+ this.axisTitle && this.axisTitle.textStr ||
563
+ this.options.id ||
564
+ this.categories && 'categories' ||
565
+ 'values'
566
+ );
423
567
  };
424
568
 
425
- // Pan along axis in a direction (1 or -1), optionally with a defined granularity (number of steps it takes to walk across current view)
569
+ // Pan along axis in a direction (1 or -1), optionally with a defined
570
+ // granularity (number of steps it takes to walk across current view)
426
571
  H.Axis.prototype.panStep = function(direction, granularity) {
427
572
  var gran = granularity || 3,
428
573
  extremes = this.getExtremes(),
@@ -456,9 +601,13 @@
456
601
  var removedSeries = this,
457
602
  hasType = false;
458
603
 
459
- // Check if any of the other series have the same type as this one. Otherwise remove it from the list.
604
+ // Check if any of the other series have the same type as this one.
605
+ // Otherwise remove it from the list.
460
606
  each(chart.series, function(s) {
461
- if (s !== removedSeries && chart.types.indexOf(removedSeries.type) < 0) {
607
+ if (
608
+ s !== removedSeries &&
609
+ chart.types.indexOf(removedSeries.type) < 0
610
+ ) {
462
611
  hasType = true;
463
612
  }
464
613
  });
@@ -469,7 +618,8 @@
469
618
  }
470
619
  });
471
620
 
472
- // Return simplified description of chart type. Some types will not be familiar to most screen reader users, but we try.
621
+ // Return simplified description of chart type. Some types will not be familiar
622
+ // to most screen reader users, but we try.
473
623
  H.Chart.prototype.getTypeDescription = function() {
474
624
  var firstType = this.types && this.types[0],
475
625
  mapTitle = this.series[0] && this.series[0].mapTitle;
@@ -493,7 +643,8 @@
493
643
  i;
494
644
 
495
645
  if (numXAxes) {
496
- desc.xAxis = 'The chart has ' + numXAxes + (numXAxes > 1 ? ' X axes' : ' X axis') + ' displaying ';
646
+ desc.xAxis = 'The chart has ' + numXAxes +
647
+ (numXAxes > 1 ? ' X axes' : ' X axis') + ' displaying ';
497
648
  if (numXAxes < 2) {
498
649
  desc.xAxis += this.xAxis[0].getDescription() + '.';
499
650
  } else {
@@ -505,7 +656,8 @@
505
656
  }
506
657
 
507
658
  if (numYAxes) {
508
- desc.yAxis = 'The chart has ' + numYAxes + (numYAxes > 1 ? ' Y axes' : ' Y axis') + ' displaying ';
659
+ desc.yAxis = 'The chart has ' + numYAxes +
660
+ (numYAxes > 1 ? ' Y axes' : ' Y axis') + ' displaying ';
509
661
  if (numYAxes < 2) {
510
662
  desc.yAxis += this.yAxis[0].getDescription() + '.';
511
663
  } else {
@@ -538,7 +690,8 @@
538
690
  }
539
691
  };
540
692
 
541
- // Highlight a point (show tooltip and display hover state). Returns the highlighted point.
693
+ // Highlight a point (show tooltip and display hover state). Returns the
694
+ // highlighted point.
542
695
  H.Point.prototype.highlight = function() {
543
696
  var chart = this.series.chart;
544
697
  if (this.graphic && this.graphic.element.focus) {
@@ -574,12 +727,6 @@
574
727
  lastSeries.points[lastSeries.points.length - 1],
575
728
  newSeries,
576
729
  newPoint,
577
- isSkipPoint = function(point) {
578
- return point.isNull &&
579
- chart.options.accessibility.keyboardNavigation.skipNullPoints ||
580
- point.series.options.skipKeyboardNavigation ||
581
- !point.series.visible;
582
- },
583
730
  // Handle connecting ends - where the points array has an extra last
584
731
  // point that is a reference to the first one. We skip this.
585
732
  forwardSkipAmount = curPoint && curPoint.series.connectEnds &&
@@ -632,6 +779,123 @@
632
779
  return newPoint.highlight();
633
780
  };
634
781
 
782
+ // Highlight first valid point in a series. Returns the point if successfully
783
+ // highlighted, otherwise false. If there is a highlighted point in the series,
784
+ // use that as starting point.
785
+ H.Series.prototype.highlightFirstValidPoint = function() {
786
+ var curPoint = this.chart.highlightedPoint,
787
+ start = curPoint.series === this ? curPoint.index : 0,
788
+ points = this.points;
789
+
790
+ for (var i = start, len = points.length; i < len; ++i) {
791
+ if (!isSkipPoint(points[i])) {
792
+ return points[i].highlight();
793
+ }
794
+ }
795
+ for (var j = start; j >= 0; --j) {
796
+ if (!isSkipPoint(points[j])) {
797
+ return points[j].highlight();
798
+ }
799
+ }
800
+ return false;
801
+ };
802
+
803
+ // Highlight next/previous series in chart. Returns false if no adjacent series
804
+ // in the direction, otherwise returns new highlighted point.
805
+ H.Chart.prototype.highlightAdjacentSeries = function(down) {
806
+ var chart = this,
807
+ newSeries,
808
+ newPoint,
809
+ adjacentNewPoint,
810
+ curPoint = chart.highlightedPoint,
811
+ lastSeries = chart.series && chart.series[chart.series.length - 1],
812
+ lastPoint = lastSeries && lastSeries.points &&
813
+ lastSeries.points[lastSeries.points.length - 1];
814
+
815
+ // If no point is highlighted, highlight the first/last point
816
+ if (!chart.highlightedPoint) {
817
+ newSeries = down ? (chart.series && chart.series[0]) : lastSeries;
818
+ newPoint = down ?
819
+ (newSeries && newSeries.points && newSeries.points[0]) : lastPoint;
820
+ return newPoint ? newPoint.highlight() : false;
821
+ }
822
+
823
+ newSeries = chart.series[curPoint.series.index + (down ? -1 : 1)];
824
+
825
+ if (!newSeries) {
826
+ return false;
827
+ }
828
+
829
+ // We have a new series in this direction, find the right point
830
+ // Weigh xDistance as counting much higher than Y distance
831
+ newPoint = getClosestPoint(curPoint, newSeries, 4);
832
+
833
+ if (!newPoint) {
834
+ return false;
835
+ }
836
+
837
+ // New series and point exists, but we might want to skip it
838
+ if (!newSeries.visible) {
839
+ // Skip the series
840
+ newPoint.highlight();
841
+ adjacentNewPoint = chart.highlightAdjacentSeries(down); // Try recurse
842
+ if (!adjacentNewPoint) {
843
+ // Recurse failed
844
+ curPoint.highlight();
845
+ return false;
846
+ }
847
+ // Recurse succeeded
848
+ return adjacentNewPoint;
849
+ }
850
+
851
+ // Highlight the new point or any first valid point back or forwards from it
852
+ newPoint.highlight();
853
+ return newPoint.series.highlightFirstValidPoint();
854
+ };
855
+
856
+ // Highlight the closest point vertically
857
+ H.Chart.prototype.highlightAdjacentPointVertical = function(down) {
858
+ var curPoint = this.highlightedPoint,
859
+ minDistance = Infinity,
860
+ bestPoint;
861
+
862
+ if (curPoint.plotX === undefined || curPoint.plotY === undefined) {
863
+ return false;
864
+ }
865
+ each(this.series, function(series) {
866
+ each(series.points, function(point) {
867
+ if (point.plotY === undefined || point.plotX === undefined ||
868
+ point === curPoint) {
869
+ return;
870
+ }
871
+ var yDistance = point.plotY - curPoint.plotY,
872
+ width = Math.abs(point.plotX - curPoint.plotX),
873
+ distance = Math.abs(yDistance) * Math.abs(yDistance) +
874
+ width * width * 4; // Weigh horizontal distance highly
875
+
876
+ // Reverse distance number if axis is reversed
877
+ if (series.yAxis.reversed) {
878
+ yDistance *= -1;
879
+ }
880
+
881
+ if (
882
+ yDistance < 0 && down || yDistance > 0 && !down || // Wrong dir
883
+ distance < 5 || // Points in same spot => infinite loop
884
+ isSkipPoint(point)
885
+ ) {
886
+ return;
887
+ }
888
+
889
+ if (distance < minDistance) {
890
+ minDistance = distance;
891
+ bestPoint = point;
892
+ }
893
+ });
894
+ });
895
+
896
+ return bestPoint ? bestPoint.highlight() : false;
897
+ };
898
+
635
899
  // Show the export menu and focus the first item (if exists)
636
900
  H.Chart.prototype.showExportMenu = function() {
637
901
  if (this.exportSVGElements && this.exportSVGElements[0]) {
@@ -643,9 +907,15 @@
643
907
  // Highlight export menu item by index
644
908
  H.Chart.prototype.highlightExportItem = function(ix) {
645
909
  var listItem = this.exportDivElements && this.exportDivElements[ix],
646
- curHighlighted = this.exportDivElements && this.exportDivElements[this.highlightedExportItem];
910
+ curHighlighted =
911
+ this.exportDivElements &&
912
+ this.exportDivElements[this.highlightedExportItem];
647
913
 
648
- if (listItem && listItem.tagName === 'DIV' && !(listItem.children && listItem.children.length)) {
914
+ if (
915
+ listItem &&
916
+ listItem.tagName === 'DIV' &&
917
+ !(listItem.children && listItem.children.length)
918
+ ) {
649
919
  if (listItem.focus) {
650
920
  listItem.focus();
651
921
  }
@@ -665,7 +935,9 @@
665
935
  var buttons = this.rangeSelector.buttons;
666
936
  // Deselect old
667
937
  if (buttons[this.highlightedRangeSelectorItemIx]) {
668
- buttons[this.highlightedRangeSelectorItemIx].setState(this.oldRangeSelectorItemState || 0);
938
+ buttons[this.highlightedRangeSelectorItemIx].setState(
939
+ this.oldRangeSelectorItemState || 0
940
+ );
669
941
  }
670
942
  // Select new
671
943
  this.highlightedRangeSelectorItemIx = ix;
@@ -684,7 +956,10 @@
684
956
  H.Chart.prototype.highlightLegendItem = function(ix) {
685
957
  var items = this.legend.allItems;
686
958
  if (items[this.highlightedLegendItemIx]) {
687
- fireEvent(items[this.highlightedLegendItemIx].legendGroup.element, 'mouseout');
959
+ fireEvent(
960
+ items[this.highlightedLegendItemIx].legendGroup.element,
961
+ 'mouseout'
962
+ );
688
963
  }
689
964
  this.highlightedLegendItemIx = ix;
690
965
  if (items[ix]) {
@@ -704,7 +979,10 @@
704
979
  each(exportList, function(el) {
705
980
  fireEvent(el, 'mouseleave');
706
981
  });
707
- if (exportList[this.highlightedExportItem] && exportList[this.highlightedExportItem].onmouseout) {
982
+ if (
983
+ exportList[this.highlightedExportItem] &&
984
+ exportList[this.highlightedExportItem].onmouseout
985
+ ) {
708
986
  exportList[this.highlightedExportItem].onmouseout();
709
987
  }
710
988
  this.highlightedExportItem = 0;
@@ -716,12 +994,13 @@
716
994
  H.Chart.prototype.addKeyboardNavEvents = function() {
717
995
  var chart = this;
718
996
 
719
- // Abstraction layer for keyboard navigation. Keep a map of keyCodes to handler functions, and a next/prev move handler for tab order.
720
- // The module's keyCode handlers determine when to move to another module.
721
- // Validate holds a function to determine if there are prerequisites for this module to run that are not met.
722
- // Init holds a function to run once before any keyCodes are interpreted.
723
- // Terminate holds a function to run once before moving to next/prev module.
724
- // transformTabs determines whether to transform tabs to left/right events or not. Defaults to true.
997
+ // Abstraction layer for keyboard navigation. Keep a map of keyCodes to
998
+ // handler functions, and a next/prev move handler for tab order. The
999
+ // module's keyCode handlers determine when to move to another module.
1000
+ // Validate holds a function to determine if there are prerequisites for
1001
+ // this module to run that are not met. Init holds a function to run once
1002
+ // before any keyCodes are interpreted. Terminate holds a function to run
1003
+ // once before moving to next/prev module.
725
1004
  function KeyboardNavigationModule(options) {
726
1005
  this.id = options.id;
727
1006
  this.keyCodeMap = options.keyCodeMap;
@@ -729,36 +1008,47 @@
729
1008
  this.validate = options.validate;
730
1009
  this.init = options.init;
731
1010
  this.terminate = options.terminate;
732
- this.transformTabs = options.transformTabs !== false;
733
1011
  }
734
1012
  KeyboardNavigationModule.prototype = {
735
1013
  // Find handler function(s) for key code in the keyCodeMap and run it.
736
1014
  run: function(e) {
737
1015
  var navModule = this,
738
1016
  keyCode = e.which || e.keyCode,
1017
+ found = false,
739
1018
  handled = false;
740
- keyCode = this.transformTabs && keyCode === 9 ? (e.shiftKey ? 37 : 39) : keyCode; // Transform tabs
741
1019
  each(this.keyCodeMap, function(codeSet) {
742
1020
  if (codeSet[0].indexOf(keyCode) > -1) {
743
- handled = codeSet[1].call(navModule, keyCode, e) === false ? false : true; // If explicitly returning false, we haven't handled it
1021
+ found = true;
1022
+ handled = codeSet[1].call(navModule, keyCode, e) === false ?
1023
+ // If explicitly returning false, we haven't handled it
1024
+ false :
1025
+ true;
744
1026
  }
745
1027
  });
1028
+ // Default tab handler, move to next/prev module
1029
+ if (!found && keyCode === 9) {
1030
+ handled = this.move(e.shiftKey ? -1 : 1);
1031
+ }
746
1032
  return handled;
747
1033
  }
748
1034
  };
749
- // Maintain abstraction between KeyboardNavigationModule and Highcharts
750
- // The chart object keeps track of a list of KeyboardNavigationModules that we move through
1035
+ // Maintain abstraction between KeyboardNavigationModule and Highcharts.
1036
+ // The chart object keeps track of a list of KeyboardNavigationModules that
1037
+ // we move through.
751
1038
  function navModuleFactory(id, keyMap, options) {
752
1039
  return new KeyboardNavigationModule(merge({
753
1040
  keyCodeMap: keyMap,
754
- // Move to next/prev valid module, or undefined if none, and init it.
755
- // Returns true on success and false if there is no valid module to move to.
1041
+ // Move to next/prev valid module, or undefined if none, and init
1042
+ // it. Returns true on success and false if there is no valid module
1043
+ // to move to.
756
1044
  move: function(direction) {
757
1045
  if (this.terminate) {
758
1046
  this.terminate(direction);
759
1047
  }
760
1048
  chart.keyboardNavigationModuleIndex += direction;
761
- var newModule = chart.keyboardNavigationModules[chart.keyboardNavigationModuleIndex];
1049
+ var newModule = chart.keyboardNavigationModules[
1050
+ chart.keyboardNavigationModuleIndex
1051
+ ];
762
1052
  if (newModule) {
763
1053
  if (newModule.validate && !newModule.validate()) {
764
1054
  return this.move(direction); // Invalid module
@@ -770,7 +1060,14 @@
770
1060
  }
771
1061
  // No module
772
1062
  chart.keyboardNavigationModuleIndex = 0; // Reset counter
773
- chart.slipNextTab = true; // Allow next tab to slip, as we will have focus on chart now
1063
+
1064
+ // Set focus to chart or exit anchor depending on direction
1065
+ if (direction > 0) {
1066
+ chart.tabExitAnchor.focus();
1067
+ } else {
1068
+ chart.renderTo.focus();
1069
+ }
1070
+
774
1071
  return false;
775
1072
  }
776
1073
  }, {
@@ -781,56 +1078,49 @@
781
1078
  // Route keydown events
782
1079
  function keydownHandler(ev) {
783
1080
  var e = ev || win.event,
784
- keyCode = e.which || e.keyCode,
785
- curNavModule = chart.keyboardNavigationModules[chart.keyboardNavigationModuleIndex];
786
-
787
- // Handle tabbing
788
- if (keyCode === 9) {
789
- // If we reached end of chart, we need to let this tab slip through to allow users to tab further
790
- if (chart.slipNextTab) {
791
- chart.slipNextTab = false;
792
- return;
793
- }
794
- }
795
- // If key was not tab, don't slip the next tab
796
- chart.slipNextTab = false;
1081
+ curNavModule = chart.keyboardNavigationModules[
1082
+ chart.keyboardNavigationModuleIndex
1083
+ ];
797
1084
 
798
- // If there is a navigation module for the current index, run it. Otherwise, we are outside of the chart in some direction.
1085
+ // If there is a navigation module for the current index, run it.
1086
+ // Otherwise, we are outside of the chart in some direction.
799
1087
  if (curNavModule) {
800
1088
  if (curNavModule.run(e)) {
801
- e.preventDefault(); // If successfully handled, stop the event here.
1089
+ // Successfully handled this key event, stop default handling
1090
+ e.preventDefault();
802
1091
  }
803
1092
  }
804
1093
  }
805
1094
 
806
- // List of the different keyboard handling modes we use depending on where we are in the chart.
807
- // Each mode has a set of handling functions mapped to key codes.
808
- // Each mode determines when to move to the next/prev mode.
1095
+ // List of the different keyboard handling modes we use depending on where
1096
+ // we are in the chart. Each mode has a set of handling functions mapped to
1097
+ // key codes. Each mode determines when to move to the next/prev mode.
809
1098
  chart.keyboardNavigationModules = [
1099
+ // Entry point catching the first tab, allowing users to tab into points
1100
+ // more intuitively.
1101
+ navModuleFactory('entry', []),
1102
+
810
1103
  // Points
1104
+ // Prevents default and ignores failure regardless
811
1105
  navModuleFactory('points', [
812
1106
  // Left/Right
813
1107
  [
814
1108
  [37, 39],
815
1109
  function(keyCode) {
816
- if (!chart.highlightAdjacentPoint(keyCode === 39)) { // Try to highlight adjacent point
817
- return this.move(keyCode === 39 ? 1 : -1); // Failed. Move to next/prev module
818
- }
1110
+ chart.highlightAdjacentPoint(keyCode === 39);
1111
+ return true;
819
1112
  }
820
1113
  ],
821
1114
  // Up/Down
822
1115
  [
823
1116
  [38, 40],
824
1117
  function(keyCode) {
825
- var newSeries;
826
- if (chart.highlightedPoint) {
827
- newSeries = chart.series[chart.highlightedPoint.series.index + (keyCode === 38 ? -1 : 1)]; // Find prev/next series
828
- if (newSeries && newSeries.points[0]) { // If series exists and has data, go for it
829
- newSeries.points[0].highlight();
830
- } else {
831
- return this.move(keyCode === 40 ? 1 : -1); // Otherwise, attempt to move to next/prev module
832
- }
833
- }
1118
+ var highlightMethod = chart.highlightedPoint &&
1119
+ chart.highlightedPoint.series.keyboardMoveVertical ?
1120
+ 'highlightAdjacentPointVertical' :
1121
+ 'highlightAdjacentSeries';
1122
+ chart[highlightMethod](keyCode !== 38);
1123
+ return true;
834
1124
  }
835
1125
  ],
836
1126
  // Enter/Spacebar
@@ -846,6 +1136,15 @@
846
1136
  // Always start highlighting from scratch when entering this module
847
1137
  init: function() {
848
1138
  delete chart.highlightedPoint;
1139
+ // Find first valid point to highlight
1140
+ for (var i = 0; i < chart.series.length; ++i) {
1141
+ for (var j = 0, len = chart.series[i].points &&
1142
+ chart.series[i].points.length; j < len; ++j) {
1143
+ if (!isSkipPoint(chart.series[i].points[j])) {
1144
+ return chart.series[i].points[j].highlight();
1145
+ }
1146
+ }
1147
+ }
849
1148
  },
850
1149
  // If leaving points, don't show tooltip anymore
851
1150
  terminate: function() {
@@ -853,9 +1152,7 @@
853
1152
  chart.tooltip.hide(0);
854
1153
  }
855
1154
  delete chart.highlightedPoint;
856
- },
857
-
858
- transformTabs: chart.options.accessibility.keyboardNavigation.tabThroughPoints
1155
+ }
859
1156
  }),
860
1157
 
861
1158
  // Exporting
@@ -866,7 +1163,8 @@
866
1163
  function() {
867
1164
  var i = chart.highlightedExportItem || 0,
868
1165
  reachedEnd = true;
869
- // Try to highlight prev item in list. Highlighting e.g. separators will fail.
1166
+ // Try to highlight prev item in list. Highlighting e.g.
1167
+ // separators will fail.
870
1168
  while (i--) {
871
1169
  if (chart.highlightExportItem(i)) {
872
1170
  reachedEnd = false;
@@ -885,8 +1183,12 @@
885
1183
  function() {
886
1184
  var highlightedExportItem = chart.highlightedExportItem || 0,
887
1185
  reachedEnd = true;
888
- // Try to highlight next item in list. Highlighting e.g. separators will fail.
889
- for (var i = highlightedExportItem + 1; i < chart.exportDivElements.length; ++i) {
1186
+ // Try to highlight next item in list. Highlighting e.g.
1187
+ // separators will fail.
1188
+ for (
1189
+ var i = highlightedExportItem + 1; i < chart.exportDivElements.length;
1190
+ ++i
1191
+ ) {
890
1192
  if (chart.highlightExportItem(i)) {
891
1193
  reachedEnd = false;
892
1194
  break;
@@ -902,19 +1204,29 @@
902
1204
  [
903
1205
  [13, 32],
904
1206
  function() {
905
- fakeClickEvent(chart.exportDivElements[chart.highlightedExportItem]);
1207
+ fakeClickEvent(
1208
+ chart.exportDivElements[chart.highlightedExportItem]
1209
+ );
906
1210
  }
907
1211
  ]
908
1212
  ], {
909
- // Only run exporting navigation if exporting support exists and is enabled on chart
1213
+ // Only run exporting navigation if exporting support exists and is
1214
+ // enabled on chart
910
1215
  validate: function() {
911
- return chart.exportChart && !(chart.options.exporting && chart.options.exporting.enabled === false);
1216
+ return (
1217
+ chart.exportChart &&
1218
+ !(
1219
+ chart.options.exporting &&
1220
+ chart.options.exporting.enabled === false
1221
+ )
1222
+ );
912
1223
  },
913
1224
  // Show export menu
914
1225
  init: function(direction) {
915
1226
  chart.highlightedPoint = null;
916
1227
  chart.showExportMenu();
917
- // If coming back to export menu from other module, try to highlight last item in menu
1228
+ // If coming back to export menu from other module, try to
1229
+ // highlight last item in menu
918
1230
  if (direction < 0 && chart.exportDivElements) {
919
1231
  for (var i = chart.exportDivElements.length; i > -1; --i) {
920
1232
  if (chart.highlightExportItem(i)) {
@@ -922,6 +1234,10 @@
922
1234
  }
923
1235
  }
924
1236
  }
1237
+ },
1238
+ // Hide the menu
1239
+ terminate: function() {
1240
+ chart.hideExportMenu();
925
1241
  }
926
1242
  }),
927
1243
 
@@ -931,7 +1247,8 @@
931
1247
  [
932
1248
  [38, 40, 37, 39],
933
1249
  function(keyCode) {
934
- chart[keyCode === 38 || keyCode === 40 ? 'yAxis' : 'xAxis'][0].panStep(keyCode < 39 ? -1 : 1);
1250
+ chart[keyCode === 38 || keyCode === 40 ? 'yAxis' : 'xAxis'][0]
1251
+ .panStep(keyCode < 39 ? -1 : 1);
935
1252
  }
936
1253
  ],
937
1254
 
@@ -940,10 +1257,15 @@
940
1257
  [9],
941
1258
  function(keyCode, e) {
942
1259
  var button;
943
- chart.mapNavButtons[chart.focusedMapNavButtonIx].setState(0); // Deselect old
944
- if (e.shiftKey && !chart.focusedMapNavButtonIx || !e.shiftKey && chart.focusedMapNavButtonIx) { // trying to go somewhere we can't?
1260
+ // Deselect old
1261
+ chart.mapNavButtons[chart.focusedMapNavButtonIx].setState(0);
1262
+ if (
1263
+ e.shiftKey && !chart.focusedMapNavButtonIx ||
1264
+ !e.shiftKey && chart.focusedMapNavButtonIx
1265
+ ) { // trying to go somewhere we can't?
945
1266
  chart.mapZoom(); // Reset zoom
946
- return this.move(e.shiftKey ? -1 : 1); // Nowhere to go, go to prev/next module
1267
+ // Nowhere to go, go to prev/next module
1268
+ return this.move(e.shiftKey ? -1 : 1);
947
1269
  }
948
1270
  chart.focusedMapNavButtonIx += e.shiftKey ? -1 : 1;
949
1271
  button = chart.mapNavButtons[chart.focusedMapNavButtonIx];
@@ -958,18 +1280,21 @@
958
1280
  [
959
1281
  [13, 32],
960
1282
  function() {
961
- fakeClickEvent(chart.mapNavButtons[chart.focusedMapNavButtonIx].element);
1283
+ fakeClickEvent(
1284
+ chart.mapNavButtons[chart.focusedMapNavButtonIx].element
1285
+ );
962
1286
  }
963
1287
  ]
964
1288
  ], {
965
1289
  // Only run this module if we have map zoom on the chart
966
1290
  validate: function() {
967
- return chart.mapZoom && chart.mapNavButtons && chart.mapNavButtons.length === 2;
1291
+ return (
1292
+ chart.mapZoom &&
1293
+ chart.mapNavButtons &&
1294
+ chart.mapNavButtons.length === 2
1295
+ );
968
1296
  },
969
1297
 
970
- // Handle tabs separately
971
- transformTabs: false,
972
-
973
1298
  // Make zoom buttons do their magic
974
1299
  init: function(direction) {
975
1300
  var zoomIn = chart.mapNavButtons[0],
@@ -979,7 +1304,10 @@
979
1304
  each(chart.mapNavButtons, function(button, i) {
980
1305
  button.element.setAttribute('tabindex', -1);
981
1306
  button.element.setAttribute('role', 'button');
982
- button.element.setAttribute('aria-label', 'Zoom ' + (i ? 'out' : '') + 'chart');
1307
+ button.element.setAttribute(
1308
+ 'aria-label',
1309
+ 'Zoom ' + (i ? 'out' : '') + 'chart'
1310
+ );
983
1311
  });
984
1312
 
985
1313
  if (initialButton.element.focus) {
@@ -998,7 +1326,9 @@
998
1326
  function(keyCode) {
999
1327
  var direction = (keyCode === 37 || keyCode === 38) ? -1 : 1;
1000
1328
  // Try to highlight next/prev button
1001
- if (!chart.highlightRangeSelectorButton(chart.highlightedRangeSelectorItemIx + direction)) {
1329
+ if (!chart.highlightRangeSelectorButton(
1330
+ chart.highlightedRangeSelectorItemIx + direction
1331
+ )) {
1002
1332
  return this.move(direction);
1003
1333
  }
1004
1334
  }
@@ -1007,15 +1337,24 @@
1007
1337
  [
1008
1338
  [13, 32],
1009
1339
  function() {
1010
- if (chart.oldRangeSelectorItemState !== 3) { // Don't allow click if button used to be disabled
1011
- fakeClickEvent(chart.rangeSelector.buttons[chart.highlightedRangeSelectorItemIx].element);
1340
+ // Don't allow click if button used to be disabled
1341
+ if (chart.oldRangeSelectorItemState !== 3) {
1342
+ fakeClickEvent(
1343
+ chart.rangeSelector.buttons[
1344
+ chart.highlightedRangeSelectorItemIx
1345
+ ].element
1346
+ );
1012
1347
  }
1013
1348
  }
1014
1349
  ]
1015
1350
  ], {
1016
1351
  // Only run this module if we have range selector
1017
1352
  validate: function() {
1018
- return chart.rangeSelector && chart.rangeSelector.buttons && chart.rangeSelector.buttons.length;
1353
+ return (
1354
+ chart.rangeSelector &&
1355
+ chart.rangeSelector.buttons &&
1356
+ chart.rangeSelector.buttons.length
1357
+ );
1019
1358
  },
1020
1359
 
1021
1360
  // Make elements focusable and accessible
@@ -1023,10 +1362,15 @@
1023
1362
  each(chart.rangeSelector.buttons, function(button) {
1024
1363
  button.element.setAttribute('tabindex', '-1');
1025
1364
  button.element.setAttribute('role', 'button');
1026
- button.element.setAttribute('aria-label', 'Select range ' + (button.text && button.text.textStr));
1365
+ button.element.setAttribute(
1366
+ 'aria-label',
1367
+ 'Select range ' + (button.text && button.text.textStr)
1368
+ );
1027
1369
  });
1028
1370
  // Focus first/last button
1029
- chart.highlightRangeSelectorButton(direction > 0 ? 0 : chart.rangeSelector.buttons.length - 1);
1371
+ chart.highlightRangeSelectorButton(
1372
+ direction > 0 ? 0 : chart.rangeSelector.buttons.length - 1
1373
+ );
1030
1374
  }
1031
1375
  }),
1032
1376
 
@@ -1036,29 +1380,42 @@
1036
1380
  [
1037
1381
  [9, 38, 40],
1038
1382
  function(keyCode, e) {
1039
- var direction = (keyCode === 9 && e.shiftKey || keyCode === 38) ? -1 : 1,
1040
- newIx = chart.highlightedInputRangeIx = chart.highlightedInputRangeIx + direction;
1383
+ var direction =
1384
+ (keyCode === 9 && e.shiftKey || keyCode === 38) ? -1 : 1,
1385
+
1386
+ newIx = chart.highlightedInputRangeIx =
1387
+ chart.highlightedInputRangeIx + direction;
1388
+
1041
1389
  // Try to highlight next/prev item in list.
1042
1390
  if (newIx > 1 || newIx < 0) { // Out of range
1043
1391
  return this.move(direction);
1044
1392
  }
1045
- chart.rangeSelector[newIx ? 'maxInput' : 'minInput'].focus(); // Input boxes are HTML, and should have focus support in all browsers
1393
+ chart.rangeSelector[newIx ? 'maxInput' : 'minInput'].focus();
1046
1394
  }
1047
1395
  ]
1048
1396
  ], {
1049
1397
  // Only run if we have range selector with input boxes
1050
1398
  validate: function() {
1051
- var inputVisible = chart.rangeSelector && chart.rangeSelector.inputGroup && chart.rangeSelector.inputGroup.element.getAttribute('visibility') !== 'hidden';
1052
- return inputVisible && chart.options.rangeSelector.inputEnabled !== false && chart.rangeSelector.minInput && chart.rangeSelector.maxInput;
1399
+ var inputVisible = (
1400
+ chart.rangeSelector &&
1401
+ chart.rangeSelector.inputGroup &&
1402
+ chart.rangeSelector.inputGroup.element
1403
+ .getAttribute('visibility') !== 'hidden'
1404
+ );
1405
+ return (
1406
+ inputVisible &&
1407
+ chart.options.rangeSelector.inputEnabled !== false &&
1408
+ chart.rangeSelector.minInput &&
1409
+ chart.rangeSelector.maxInput
1410
+ );
1053
1411
  },
1054
1412
 
1055
- // Handle tabs different from left/right (because we don't want to catch left/right in a text area)
1056
- transformTabs: false,
1057
-
1058
1413
  // Highlight first/last input box
1059
1414
  init: function(direction) {
1060
1415
  chart.highlightedInputRangeIx = direction > 0 ? 0 : 1;
1061
- chart.rangeSelector[chart.highlightedInputRangeIx ? 'maxInput' : 'minInput'].focus();
1416
+ chart.rangeSelector[
1417
+ chart.highlightedInputRangeIx ? 'maxInput' : 'minInput'
1418
+ ].focus();
1062
1419
  }
1063
1420
  }),
1064
1421
 
@@ -1070,7 +1427,9 @@
1070
1427
  function(keyCode) {
1071
1428
  var direction = (keyCode === 37 || keyCode === 38) ? -1 : 1;
1072
1429
  // Try to highlight next/prev legend item
1073
- if (!chart.highlightLegendItem(chart.highlightedLegendItemIx + direction)) {
1430
+ if (!chart.highlightLegendItem(
1431
+ chart.highlightedLegendItemIx + direction
1432
+ )) {
1074
1433
  return this.move(direction);
1075
1434
  }
1076
1435
  }
@@ -1079,12 +1438,16 @@
1079
1438
  [
1080
1439
  [13, 32],
1081
1440
  function() {
1082
- fakeClickEvent(chart.legend.allItems[chart.highlightedLegendItemIx].legendItem.element.parentNode);
1441
+ fakeClickEvent(
1442
+ chart.legend.allItems[
1443
+ chart.highlightedLegendItemIx
1444
+ ].legendItem.element.parentNode
1445
+ );
1083
1446
  }
1084
1447
  ]
1085
1448
  ], {
1086
- // Only run this module if we have at least one legend - wait for it - item.
1087
- // Don't run if the legend is populated by a colorAxis.
1449
+ // Only run this module if we have at least one legend - wait for
1450
+ // it - item. Don't run if the legend is populated by a colorAxis.
1088
1451
  // Don't run if legend navigation is disabled.
1089
1452
  validate: function() {
1090
1453
  return chart.legend && chart.legend.allItems &&
@@ -1099,15 +1462,22 @@
1099
1462
  each(chart.legend.allItems, function(item) {
1100
1463
  item.legendGroup.element.setAttribute('tabindex', '-1');
1101
1464
  item.legendGroup.element.setAttribute('role', 'button');
1102
- item.legendGroup.element.setAttribute('aria-label', 'Toggle visibility of series ' + item.name);
1465
+ item.legendGroup.element.setAttribute(
1466
+ 'aria-label',
1467
+ 'Toggle visibility of series ' + item.name
1468
+ );
1103
1469
  });
1104
1470
  // Focus first/last item
1105
- chart.highlightLegendItem(direction > 0 ? 0 : chart.legend.allItems.length - 1);
1471
+ chart.highlightLegendItem(
1472
+ direction > 0 ? 0 : chart.legend.allItems.length - 1
1473
+ );
1106
1474
  }
1107
1475
  })
1108
1476
  ];
1109
1477
 
1110
- // Init nav module index. We start at the first module, and as the user navigates through the chart the index will increase to use different handler modules.
1478
+ // Init nav module index. We start at the first module, and as the user
1479
+ // navigates through the chart the index will increase to use different
1480
+ // handler modules.
1111
1481
  chart.keyboardNavigationModuleIndex = 0;
1112
1482
 
1113
1483
  // Make chart reachable by tab
@@ -1118,6 +1488,17 @@
1118
1488
  chart.container.setAttribute('tabindex', '0');
1119
1489
  }
1120
1490
 
1491
+ // Add tab exit anchor
1492
+ // We use this to move focus out of chart whenever we want, by setting focus
1493
+ // to this and not preventing the default tab action.
1494
+ if (!chart.tabExitAnchor) {
1495
+ chart.tabExitAnchor = doc.createElement('div');
1496
+ // Not reachable by user
1497
+ chart.tabExitAnchor.setAttribute('tabindex', '-1');
1498
+ merge(true, chart.tabExitAnchor.style, hiddenStyle);
1499
+ chart.renderTo.appendChild(chart.tabExitAnchor);
1500
+ }
1501
+
1121
1502
  // Handle keyboard events
1122
1503
  addEvent(chart.renderTo, 'keydown', keydownHandler);
1123
1504
  addEvent(chart, 'destroy', function() {
@@ -1126,7 +1507,8 @@
1126
1507
  };
1127
1508
 
1128
1509
  // Add screen reader region to chart.
1129
- // tableId is the HTML id of the table to focus when clicking the table anchor in the screen reader region.
1510
+ // tableId is the HTML id of the table to focus when clicking the table anchor
1511
+ // in the screen reader region.
1130
1512
  H.Chart.prototype.addScreenReaderRegion = function(id, tableId) {
1131
1513
  var chart = this,
1132
1514
  series = chart.series,
@@ -1136,32 +1518,53 @@
1136
1518
  tableShortcut = doc.createElement('h4'),
1137
1519
  tableShortcutAnchor = doc.createElement('a'),
1138
1520
  chartHeading = doc.createElement('h4'),
1139
- hiddenStyle = { // CSS style to hide element from visual users while still exposing it to screen readers
1140
- position: 'absolute',
1141
- left: '-9999px',
1142
- top: 'auto',
1143
- width: '1px',
1144
- height: '1px',
1145
- overflow: 'hidden'
1146
- },
1147
1521
  chartTypes = chart.types || [],
1148
- // Build axis info - but not for pies and maps. Consider not adding for certain other types as well (funnel, pyramid?)
1149
- axesDesc = (chartTypes.length === 1 && chartTypes[0] === 'pie' || chartTypes[0] === 'map') && {} || chart.getAxesDescription(),
1150
- chartTypeInfo = series[0] && typeToSeriesMap[series[0].type] || typeToSeriesMap['default']; // eslint-disable-line dot-notation
1522
+ // Build axis info - but not for pies and maps. Consider not adding for
1523
+ // certain other types as well (funnel, pyramid?)
1524
+ axesDesc = (
1525
+ chartTypes.length === 1 && chartTypes[0] === 'pie' ||
1526
+ chartTypes[0] === 'map'
1527
+ ) && {} || chart.getAxesDescription(),
1528
+ chartTypeInfo = series[0] && typeToSeriesMap[series[0].type] ||
1529
+ typeToSeriesMap['default']; // eslint-disable-line dot-notation
1151
1530
 
1152
1531
  hiddenSection.setAttribute('id', id);
1153
1532
  hiddenSection.setAttribute('role', 'region');
1154
- hiddenSection.setAttribute('aria-label', 'Chart screen reader information.');
1155
-
1156
- hiddenSection.innerHTML = a11yOptions.screenReaderSectionFormatter && a11yOptions.screenReaderSectionFormatter(chart) ||
1533
+ hiddenSection.setAttribute(
1534
+ 'aria-label',
1535
+ 'Chart screen reader information.'
1536
+ );
1537
+
1538
+ hiddenSection.innerHTML =
1539
+ a11yOptions.screenReaderSectionFormatter &&
1540
+ a11yOptions.screenReaderSectionFormatter(chart) ||
1157
1541
  '<div>Use regions/landmarks to skip ahead to chart' +
1158
1542
  (series.length > 1 ? ' and navigate between data series' : '') +
1159
- '.</div><h3>' + (options.title.text ? htmlencode(options.title.text) : 'Chart') +
1160
- (options.subtitle && options.subtitle.text ? '. ' + htmlencode(options.subtitle.text) : '') +
1161
- '</h3><h4>Long description.</h4><div>' + (options.chart.description || 'No description available.') +
1162
- '</div><h4>Structure.</h4><div>Chart type: ' + (options.chart.typeDescription || chart.getTypeDescription()) + '</div>' +
1163
- (series.length === 1 ? '<div>' + chartTypeInfo[0] + ' with ' + series[0].points.length + ' ' +
1164
- (series[0].points.length === 1 ? chartTypeInfo[1] : chartTypeInfo[2]) + '.</div>' : '') +
1543
+ '.</div><h3>' +
1544
+ (options.title.text ? htmlencode(options.title.text) : 'Chart') +
1545
+ (
1546
+ options.subtitle && options.subtitle.text ?
1547
+ '. ' + htmlencode(options.subtitle.text) :
1548
+ ''
1549
+ ) +
1550
+ '</h3><h4>Long description.</h4><div>' +
1551
+ (options.chart.description || 'No description available.') +
1552
+ '</div><h4>Structure.</h4><div>Chart type: ' +
1553
+ (options.chart.typeDescription || chart.getTypeDescription()) +
1554
+ '</div>' +
1555
+ (
1556
+ series.length === 1 ?
1557
+ (
1558
+ '<div>' + chartTypeInfo[0] + ' with ' +
1559
+ series[0].points.length + ' ' +
1560
+ (
1561
+ series[0].points.length === 1 ?
1562
+ chartTypeInfo[1] :
1563
+ chartTypeInfo[2]
1564
+ ) +
1565
+ '.</div>'
1566
+ ) : ''
1567
+ ) +
1165
1568
  (axesDesc.xAxis ? ('<div>' + axesDesc.xAxis + '</div>') : '') +
1166
1569
  (axesDesc.yAxis ? ('<div>' + axesDesc.yAxis + '</div>') : '');
1167
1570
 
@@ -1169,11 +1572,13 @@
1169
1572
  if (chart.getCSV) {
1170
1573
  tableShortcutAnchor.innerHTML = 'View as data table.';
1171
1574
  tableShortcutAnchor.href = '#' + tableId;
1172
- tableShortcutAnchor.setAttribute('tabindex', '-1'); // Make this unreachable by user tabbing
1173
- tableShortcutAnchor.onclick = a11yOptions.onTableAnchorClick || function() {
1174
- chart.viewData();
1175
- doc.getElementById(tableId).focus();
1176
- };
1575
+ // Make this unreachable by user tabbing
1576
+ tableShortcutAnchor.setAttribute('tabindex', '-1');
1577
+ tableShortcutAnchor.onclick =
1578
+ a11yOptions.onTableAnchorClick || function() {
1579
+ chart.viewData();
1580
+ doc.getElementById(tableId).focus();
1581
+ };
1177
1582
  tableShortcut.appendChild(tableShortcutAnchor);
1178
1583
  hiddenSection.appendChild(tableShortcut);
1179
1584
  }
@@ -1199,15 +1604,25 @@
1199
1604
  return;
1200
1605
  }
1201
1606
 
1202
- var titleElement = doc.createElementNS('http://www.w3.org/2000/svg', 'title'),
1203
- exportGroupElement = doc.createElementNS('http://www.w3.org/2000/svg', 'g'),
1607
+ var titleElement = doc.createElementNS(
1608
+ 'http://www.w3.org/2000/svg',
1609
+ 'title'
1610
+ ),
1611
+ exportGroupElement = doc.createElementNS(
1612
+ 'http://www.w3.org/2000/svg',
1613
+ 'g'
1614
+ ),
1204
1615
  descElement = chart.container.getElementsByTagName('desc')[0],
1205
1616
  textElements = chart.container.getElementsByTagName('text'),
1206
1617
  titleId = 'highcharts-title-' + chart.index,
1207
1618
  tableId = 'highcharts-data-table-' + chart.index,
1208
1619
  hiddenSectionId = 'highcharts-information-region-' + chart.index,
1209
1620
  chartTitle = options.title.text || 'Chart',
1210
- oldColumnHeaderFormatter = options.exporting && options.exporting.csv && options.exporting.csv.columnHeaderFormatter,
1621
+ oldColumnHeaderFormatter = (
1622
+ options.exporting &&
1623
+ options.exporting.csv &&
1624
+ options.exporting.csv.columnHeaderFormatter
1625
+ ),
1211
1626
  topLevelColumns = [];
1212
1627
 
1213
1628
  // Add SVG title/desc tags
@@ -1215,35 +1630,51 @@
1215
1630
  titleElement.id = titleId;
1216
1631
  descElement.parentNode.insertBefore(titleElement, descElement);
1217
1632
  chart.renderTo.setAttribute('role', 'region');
1218
- //chart.container.setAttribute('aria-details', hiddenSectionId); // JAWS currently doesn't handle this too well
1219
- chart.renderTo.setAttribute('aria-label', 'Interactive chart. ' + chartTitle +
1220
- '. Use up and down arrows to navigate with most screen readers.');
1633
+ chart.renderTo.setAttribute(
1634
+ 'aria-label',
1635
+ 'Interactive chart. ' + chartTitle +
1636
+ '. Use up and down arrows to navigate with most screen readers.'
1637
+ );
1221
1638
 
1222
1639
  // Set screen reader properties on export menu
1223
- if (chart.exportSVGElements && chart.exportSVGElements[0] && chart.exportSVGElements[0].element) {
1640
+ if (
1641
+ chart.exportSVGElements &&
1642
+ chart.exportSVGElements[0] &&
1643
+ chart.exportSVGElements[0].element
1644
+ ) {
1224
1645
  var oldExportCallback = chart.exportSVGElements[0].element.onclick,
1225
1646
  parent = chart.exportSVGElements[0].element.parentNode;
1226
1647
  chart.exportSVGElements[0].element.onclick = function() {
1227
- oldExportCallback.apply(this, Array.prototype.slice.call(arguments));
1648
+ oldExportCallback.apply(
1649
+ this,
1650
+ Array.prototype.slice.call(arguments)
1651
+ );
1228
1652
  chart.addAccessibleContextMenuAttribs();
1229
1653
  chart.highlightExportItem(0);
1230
1654
  };
1231
1655
  chart.exportSVGElements[0].element.setAttribute('role', 'button');
1232
- chart.exportSVGElements[0].element.setAttribute('aria-label', 'View export menu');
1656
+ chart.exportSVGElements[0].element.setAttribute(
1657
+ 'aria-label',
1658
+ 'View export menu'
1659
+ );
1233
1660
  exportGroupElement.appendChild(chart.exportSVGElements[0].element);
1234
1661
  exportGroupElement.setAttribute('role', 'region');
1235
1662
  exportGroupElement.setAttribute('aria-label', 'Chart export menu');
1236
1663
  parent.appendChild(exportGroupElement);
1237
1664
  }
1238
1665
 
1239
- // Set screen reader properties on input boxes for range selector. We need to do this regardless of whether or not these are visible, as they are
1666
+ // Set screen reader properties on input boxes for range selector. We need
1667
+ // to do this regardless of whether or not these are visible, as they are
1240
1668
  // by default part of the page's tabindex unless we set them to -1.
1241
1669
  if (chart.rangeSelector) {
1242
1670
  each(['minInput', 'maxInput'], function(key, i) {
1243
1671
  if (chart.rangeSelector[key]) {
1244
1672
  chart.rangeSelector[key].setAttribute('tabindex', '-1');
1245
1673
  chart.rangeSelector[key].setAttribute('role', 'textbox');
1246
- chart.rangeSelector[key].setAttribute('aria-label', 'Select ' + (i ? 'end' : 'start') + ' date.');
1674
+ chart.rangeSelector[key].setAttribute(
1675
+ 'aria-label',
1676
+ 'Select ' + (i ? 'end' : 'start') + ' date.'
1677
+ );
1247
1678
  }
1248
1679
  });
1249
1680
  }
@@ -1277,7 +1708,8 @@
1277
1708
  var prevCol = topLevelColumns[topLevelColumns.length - 1];
1278
1709
  if (keyLength > 1) {
1279
1710
  // We need multiple levels of column headers
1280
- // Populate a list of column headers to add in addition to the ones added by export-data
1711
+ // Populate a list of column headers to add in addition to
1712
+ // the ones added by export-data
1281
1713
  if ((prevCol && prevCol.text) !== item.name) {
1282
1714
  topLevelColumns.push({
1283
1715
  text: item.name,
@@ -1286,7 +1718,12 @@
1286
1718
  }
1287
1719
  }
1288
1720
  if (oldColumnHeaderFormatter) {
1289
- return oldColumnHeaderFormatter.call(this, item, key, keyLength);
1721
+ return oldColumnHeaderFormatter.call(
1722
+ this,
1723
+ item,
1724
+ key,
1725
+ keyLength
1726
+ );
1290
1727
  }
1291
1728
  return keyLength > 1 ? key : item.name;
1292
1729
  }
@@ -1296,7 +1733,11 @@
1296
1733
  // Add ID and title/caption to table HTML
1297
1734
  H.wrap(chart, 'getTable', function(proceed) {
1298
1735
  return proceed.apply(this, Array.prototype.slice.call(arguments, 1))
1299
- .replace('<table>', '<table id="' + tableId + '" summary="Table representation of chart"><caption>' + chartTitle + '</caption>');
1736
+ .replace(
1737
+ '<table>',
1738
+ '<table id="' + tableId + '" summary="Table representation ' +
1739
+ 'of chart"><caption>' + chartTitle + '</caption>'
1740
+ );
1300
1741
  });
1301
1742
 
1302
1743
  // Add accessibility attributes and top level columns
@@ -1334,7 +1775,8 @@
1334
1775
  // Add top level columns
1335
1776
  if (topLevelColumns.length) {
1336
1777
  each(topLevelColumns, function(col) {
1337
- columnHeaderRow += '<th scope="col" colspan="' + col.span + '">' + col.text + '</th>';
1778
+ columnHeaderRow += '<th scope="col" colspan="' + col.span +
1779
+ '">' + col.text + '</th>';
1338
1780
  });
1339
1781
  head.insertAdjacentHTML('afterbegin', columnHeaderRow);
1340
1782
  }