highcharts-rails 5.0.14 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  }