highcharts-rails 6.0.2 → 6.0.3
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.
- checksums.yaml +4 -4
- data/CCBYNC-LICENSE +103 -0
- data/CHANGELOG.markdown +31 -0
- data/Highsoft-LICENSE +1 -0
- data/{LICENSE → MIT-LICENSE} +0 -0
- data/README.markdown +6 -6
- data/app/assets/javascripts/highcharts.js +1635 -498
- data/app/assets/javascripts/highcharts/highcharts-3d.js +1 -1
- data/app/assets/javascripts/highcharts/highcharts-more.js +2 -2
- data/app/assets/javascripts/highcharts/modules/accessibility.js +1072 -824
- data/app/assets/javascripts/highcharts/modules/annotations.js +1 -1
- data/app/assets/javascripts/highcharts/modules/boost-canvas.js +3 -13
- data/app/assets/javascripts/highcharts/modules/boost.js +29 -13
- data/app/assets/javascripts/highcharts/modules/broken-axis.js +1 -1
- data/app/assets/javascripts/highcharts/modules/bullet.js +1 -1
- data/app/assets/javascripts/highcharts/modules/data.js +6 -6
- data/app/assets/javascripts/highcharts/modules/drag-panes.js +1 -1
- data/app/assets/javascripts/highcharts/modules/drilldown.js +1 -1
- data/app/assets/javascripts/highcharts/modules/export-data.js +10 -12
- data/app/assets/javascripts/highcharts/modules/exporting.js +1 -1
- data/app/assets/javascripts/highcharts/modules/funnel.js +1 -1
- data/app/assets/javascripts/highcharts/modules/gantt.js +26 -78
- data/app/assets/javascripts/highcharts/modules/grid-axis.js +1 -1
- data/app/assets/javascripts/highcharts/modules/heatmap.js +1 -1
- data/app/assets/javascripts/highcharts/modules/histogram-bellcurve.js +1 -1
- data/app/assets/javascripts/highcharts/modules/item-series.js +1 -1
- data/app/assets/javascripts/highcharts/modules/no-data-to-display.js +6 -15
- data/app/assets/javascripts/highcharts/modules/offline-exporting.js +2 -2
- data/app/assets/javascripts/highcharts/modules/oldie.js +2 -2
- data/app/assets/javascripts/highcharts/modules/overlapping-datalabels.js +41 -47
- data/app/assets/javascripts/highcharts/modules/parallel-coordinates.js +10 -6
- data/app/assets/javascripts/highcharts/modules/pareto.js +9 -1
- data/app/assets/javascripts/highcharts/modules/sankey.js +1 -1
- data/app/assets/javascripts/highcharts/modules/series-label.js +1 -1
- data/app/assets/javascripts/highcharts/modules/solid-gauge.js +1 -1
- data/app/assets/javascripts/highcharts/modules/static-scale.js +2 -6
- data/app/assets/javascripts/highcharts/modules/stock.js +96 -30
- data/app/assets/javascripts/highcharts/modules/streamgraph.js +1 -1
- data/app/assets/javascripts/highcharts/modules/sunburst.js +82 -50
- data/app/assets/javascripts/highcharts/modules/tilemap.js +1 -1
- data/app/assets/javascripts/highcharts/modules/treemap.js +10 -2
- data/app/assets/javascripts/highcharts/modules/variable-pie.js +1 -1
- data/app/assets/javascripts/highcharts/modules/variwide.js +1 -1
- data/app/assets/javascripts/highcharts/modules/vector.js +1 -1
- data/app/assets/javascripts/highcharts/modules/windbarb.js +1 -1
- data/app/assets/javascripts/highcharts/modules/wordcloud.js +7 -3
- data/app/assets/javascripts/highcharts/modules/xrange.js +24 -76
- data/app/assets/javascripts/highcharts/themes/avocado.js +1 -1
- data/app/assets/javascripts/highcharts/themes/dark-blue.js +1 -1
- data/app/assets/javascripts/highcharts/themes/dark-green.js +1 -1
- data/app/assets/javascripts/highcharts/themes/dark-unica.js +1 -1
- data/app/assets/javascripts/highcharts/themes/gray.js +1 -1
- data/app/assets/javascripts/highcharts/themes/grid-light.js +1 -1
- data/app/assets/javascripts/highcharts/themes/grid.js +1 -1
- data/app/assets/javascripts/highcharts/themes/sand-signika.js +1 -1
- data/app/assets/javascripts/highcharts/themes/skies.js +1 -1
- data/app/assets/javascripts/highcharts/themes/sunset.js +1 -1
- data/highcharts-rails.gemspec +1 -0
- data/lib/highcharts/version.rb +1 -1
- metadata +9 -4
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* @license Highcharts JS v6.0.
|
2
|
+
* @license Highcharts JS v6.0.3 (2017-11-14)
|
3
3
|
*
|
4
4
|
* (c) 2009-2016 Torstein Honsi
|
5
5
|
*
|
@@ -1321,7 +1321,7 @@
|
|
1321
1321
|
}
|
1322
1322
|
|
1323
1323
|
this.graphPath = linePath;
|
1324
|
-
this.areaPath =
|
1324
|
+
this.areaPath = lowerPath.concat(higherAreaPath);
|
1325
1325
|
|
1326
1326
|
// Prepare for sideways animation
|
1327
1327
|
linePath.isArea = true;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* @license Highcharts JS v6.0.
|
2
|
+
* @license Highcharts JS v6.0.3 (2017-11-14)
|
3
3
|
* Accessibility module
|
4
4
|
*
|
5
5
|
* (c) 2010-2017 Highsoft AS
|
@@ -17,7 +17,7 @@
|
|
17
17
|
}(function(Highcharts) {
|
18
18
|
(function(H) {
|
19
19
|
/**
|
20
|
-
* Accessibility module
|
20
|
+
* Accessibility module - Screen Reader support
|
21
21
|
*
|
22
22
|
* (c) 2010-2017 Highsoft AS
|
23
23
|
* Author: Oystein Moseng
|
@@ -31,8 +31,6 @@
|
|
31
31
|
each = H.each,
|
32
32
|
erase = H.erase,
|
33
33
|
addEvent = H.addEvent,
|
34
|
-
removeEvent = H.removeEvent,
|
35
|
-
fireEvent = H.fireEvent,
|
36
34
|
dateFormat = H.dateFormat,
|
37
35
|
merge = H.merge,
|
38
36
|
// CSS style to hide element from visual users while still exposing it to
|
@@ -99,27 +97,18 @@
|
|
99
97
|
'contributes towards a total end value. '
|
100
98
|
};
|
101
99
|
|
100
|
+
|
102
101
|
// If a point has one of the special keys defined, we expose all keys to the
|
103
102
|
// screen reader.
|
104
103
|
H.Series.prototype.commonKeys = ['name', 'id', 'category', 'x', 'value', 'y'];
|
105
104
|
H.Series.prototype.specialKeys = [
|
106
105
|
'z', 'open', 'high', 'q3', 'median', 'q1', 'low', 'close'
|
107
106
|
];
|
108
|
-
|
109
|
-
// A pie is always simple. Don't quote me on that.
|
110
107
|
if (H.seriesTypes.pie) {
|
108
|
+
// A pie is always simple. Don't quote me on that.
|
111
109
|
H.seriesTypes.pie.prototype.specialKeys = [];
|
112
110
|
}
|
113
111
|
|
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
|
-
|
123
112
|
|
124
113
|
/**
|
125
114
|
* Accessibility options
|
@@ -158,35 +147,7 @@
|
|
158
147
|
* @default 30
|
159
148
|
* @since 5.0.0
|
160
149
|
*/
|
161
|
-
pointDescriptionThreshold: 30
|
162
|
-
|
163
|
-
/**
|
164
|
-
* Options for keyboard navigation.
|
165
|
-
*
|
166
|
-
* @type {Object}
|
167
|
-
* @since 5.0.0
|
168
|
-
*/
|
169
|
-
keyboardNavigation: {
|
170
|
-
|
171
|
-
/**
|
172
|
-
* Enable keyboard navigation for the chart.
|
173
|
-
*
|
174
|
-
* @type {Boolean}
|
175
|
-
* @default true
|
176
|
-
* @since 5.0.0
|
177
|
-
*/
|
178
|
-
enabled: true
|
179
|
-
|
180
|
-
/**
|
181
|
-
* Skip null points when navigating through points with the
|
182
|
-
* keyboard.
|
183
|
-
*
|
184
|
-
* @type {Boolean}
|
185
|
-
* @default false
|
186
|
-
* @since 5.0.0
|
187
|
-
* @apioption accessibility.keyboardNavigation.skipNullPoints
|
188
|
-
*/
|
189
|
-
}
|
150
|
+
pointDescriptionThreshold: 30 // set to false to disable
|
190
151
|
|
191
152
|
/**
|
192
153
|
* Whether or not to add series descriptions to charts with a single
|
@@ -311,23 +272,6 @@
|
|
311
272
|
* @apioption chart.typeDescription
|
312
273
|
*/
|
313
274
|
|
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
275
|
|
332
276
|
/**
|
333
277
|
* HTML encode some characters vulnerable for XSS.
|
@@ -344,6 +288,17 @@
|
|
344
288
|
.replace(/\//g, '/');
|
345
289
|
}
|
346
290
|
|
291
|
+
/**
|
292
|
+
* Strip HTML tags away from a string. Used for aria-label attributes, painting
|
293
|
+
* on a canvas will fail if the text contains tags.
|
294
|
+
* @param {String} s The input string
|
295
|
+
* @return {String} The filtered string
|
296
|
+
*/
|
297
|
+
function stripTags(s) {
|
298
|
+
return typeof s === 'string' ? s.replace(/<\/?[^>]+(>|$)/g, '') : s;
|
299
|
+
}
|
300
|
+
|
301
|
+
|
347
302
|
// Utility function. Reverses child nodes of a DOM element
|
348
303
|
function reverseChildNodes(node) {
|
349
304
|
var i = node.childNodes.length;
|
@@ -352,52 +307,6 @@
|
|
352
307
|
}
|
353
308
|
}
|
354
309
|
|
355
|
-
// Utility function to attempt to fake a click event on an element
|
356
|
-
function fakeClickEvent(element) {
|
357
|
-
var fakeEvent;
|
358
|
-
if (element && element.onclick && doc.createEvent) {
|
359
|
-
fakeEvent = doc.createEvent('Events');
|
360
|
-
fakeEvent.initEvent('click', true, false);
|
361
|
-
element.onclick(fakeEvent);
|
362
|
-
}
|
363
|
-
}
|
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
310
|
|
402
311
|
// Whenever drawing series, put info on DOM elements
|
403
312
|
H.wrap(H.Series.prototype, 'render', function(proceed) {
|
@@ -407,6 +316,7 @@
|
|
407
316
|
}
|
408
317
|
});
|
409
318
|
|
319
|
+
|
410
320
|
// Put accessible info on series and points of a series
|
411
321
|
H.Series.prototype.setA11yDescription = function() {
|
412
322
|
var a11yOptions = this.chart.options.accessibility,
|
@@ -443,12 +353,13 @@
|
|
443
353
|
if (point.graphic) {
|
444
354
|
point.graphic.element.setAttribute('role', 'img');
|
445
355
|
point.graphic.element.setAttribute('tabindex', '-1');
|
446
|
-
point.graphic.element.setAttribute('aria-label',
|
356
|
+
point.graphic.element.setAttribute('aria-label', stripTags(
|
447
357
|
point.series.options.pointDescriptionFormatter &&
|
448
358
|
point.series.options.pointDescriptionFormatter(point) ||
|
449
359
|
a11yOptions.pointDescriptionFormatter &&
|
450
360
|
a11yOptions.pointDescriptionFormatter(point) ||
|
451
|
-
point.buildPointInfoString()
|
361
|
+
point.buildPointInfoString()
|
362
|
+
));
|
452
363
|
}
|
453
364
|
});
|
454
365
|
}
|
@@ -461,14 +372,17 @@
|
|
461
372
|
seriesEl.setAttribute('tabindex', '-1');
|
462
373
|
seriesEl.setAttribute(
|
463
374
|
'aria-label',
|
464
|
-
|
465
|
-
|
466
|
-
|
375
|
+
stripTags(
|
376
|
+
a11yOptions.seriesDescriptionFormatter &&
|
377
|
+
a11yOptions.seriesDescriptionFormatter(this) ||
|
378
|
+
this.buildSeriesInfoString()
|
379
|
+
)
|
467
380
|
);
|
468
381
|
}
|
469
382
|
}
|
470
383
|
};
|
471
384
|
|
385
|
+
|
472
386
|
// Return string with information about series
|
473
387
|
H.Series.prototype.buildSeriesInfoString = function() {
|
474
388
|
var typeInfo = (
|
@@ -501,6 +415,7 @@
|
|
501
415
|
);
|
502
416
|
};
|
503
417
|
|
418
|
+
|
504
419
|
// Return string with information about point
|
505
420
|
H.Point.prototype.buildPointInfoString = function() {
|
506
421
|
var point = this,
|
@@ -555,6 +470,7 @@
|
|
555
470
|
(this.description ? ' ' + this.description : '');
|
556
471
|
};
|
557
472
|
|
473
|
+
|
558
474
|
// Get descriptive label for axis
|
559
475
|
H.Axis.prototype.getDescription = function() {
|
560
476
|
return (
|
@@ -566,24 +482,6 @@
|
|
566
482
|
);
|
567
483
|
};
|
568
484
|
|
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)
|
571
|
-
H.Axis.prototype.panStep = function(direction, granularity) {
|
572
|
-
var gran = granularity || 3,
|
573
|
-
extremes = this.getExtremes(),
|
574
|
-
step = (extremes.max - extremes.min) / gran * direction,
|
575
|
-
newMax = extremes.max + step,
|
576
|
-
newMin = extremes.min + step,
|
577
|
-
size = newMax - newMin;
|
578
|
-
if (direction < 0 && newMin < extremes.dataMin) {
|
579
|
-
newMin = extremes.dataMin;
|
580
|
-
newMax = newMin + size;
|
581
|
-
} else if (direction > 0 && newMax > extremes.dataMax) {
|
582
|
-
newMax = extremes.dataMax;
|
583
|
-
newMin = newMax - size;
|
584
|
-
}
|
585
|
-
this.setExtremes(newMin, newMax);
|
586
|
-
};
|
587
485
|
|
588
486
|
// Whenever adding or removing series, keep track of types present in chart
|
589
487
|
H.wrap(H.Series.prototype, 'init', function(proceed) {
|
@@ -618,6 +516,7 @@
|
|
618
516
|
}
|
619
517
|
});
|
620
518
|
|
519
|
+
|
621
520
|
// Return simplified description of chart type. Some types will not be familiar
|
622
521
|
// to most screen reader users, but we try.
|
623
522
|
H.Chart.prototype.getTypeDescription = function() {
|
@@ -635,6 +534,7 @@
|
|
635
534
|
return firstType + ' chart.' + (typeDescriptionMap[firstType] || '');
|
636
535
|
};
|
637
536
|
|
537
|
+
|
638
538
|
// Return object with text description of each of the chart's axes
|
639
539
|
H.Chart.prototype.getAxesDescription = function() {
|
640
540
|
var numXAxes = this.xAxis.length,
|
@@ -671,6 +571,7 @@
|
|
671
571
|
return desc;
|
672
572
|
};
|
673
573
|
|
574
|
+
|
674
575
|
// Set a11y attribs on exporting menu
|
675
576
|
H.Chart.prototype.addAccessibleContextMenuAttribs = function() {
|
676
577
|
var exportList = this.exportDivElements;
|
@@ -690,84 +591,713 @@
|
|
690
591
|
}
|
691
592
|
};
|
692
593
|
|
693
|
-
// Highlight a point (show tooltip and display hover state). Returns the
|
694
|
-
// highlighted point.
|
695
|
-
H.Point.prototype.highlight = function() {
|
696
|
-
var chart = this.series.chart;
|
697
|
-
if (this.graphic && this.graphic.element.focus) {
|
698
|
-
this.graphic.element.focus();
|
699
|
-
}
|
700
|
-
if (!this.isNull) {
|
701
|
-
this.onMouseOver(); // Show the hover marker
|
702
|
-
// Show the tooltip
|
703
|
-
if (chart.tooltip) {
|
704
|
-
chart.tooltip.refresh(chart.tooltip.shared ? [this] : this);
|
705
|
-
}
|
706
|
-
} else {
|
707
|
-
if (chart.tooltip) {
|
708
|
-
chart.tooltip.hide(0);
|
709
|
-
}
|
710
|
-
// Don't call blur on the element, as it messes up the chart div's focus
|
711
|
-
}
|
712
|
-
chart.highlightedPoint = this;
|
713
|
-
return this;
|
714
|
-
};
|
715
594
|
|
716
|
-
//
|
717
|
-
//
|
718
|
-
//
|
719
|
-
H.Chart.prototype.
|
595
|
+
// Add screen reader region to chart.
|
596
|
+
// tableId is the HTML id of the table to focus when clicking the table anchor
|
597
|
+
// in the screen reader region.
|
598
|
+
H.Chart.prototype.addScreenReaderRegion = function(id, tableId) {
|
720
599
|
var chart = this,
|
721
600
|
series = chart.series,
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
//
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
return false;
|
738
|
-
}
|
739
|
-
|
740
|
-
if (!curPoint) {
|
741
|
-
// No point is highlighted yet. Try first/last point depending on move
|
742
|
-
// direction
|
743
|
-
newPoint = next ? series[0].points[0] : lastPoint;
|
744
|
-
} else {
|
745
|
-
// We have a highlighted point.
|
746
|
-
// Find index of current point in series.points array. Necessary for
|
747
|
-
// dataGrouping (and maybe zoom?)
|
748
|
-
if (curPoints[curPointIndex] !== curPoint) {
|
749
|
-
for (var i = 0; i < curPoints.length; ++i) {
|
750
|
-
if (curPoints[i] === curPoint) {
|
751
|
-
curPointIndex = i;
|
752
|
-
break;
|
753
|
-
}
|
754
|
-
}
|
755
|
-
}
|
601
|
+
options = chart.options,
|
602
|
+
a11yOptions = options.accessibility,
|
603
|
+
hiddenSection = chart.screenReaderRegion = doc.createElement('div'),
|
604
|
+
tableShortcut = doc.createElement('h4'),
|
605
|
+
tableShortcutAnchor = doc.createElement('a'),
|
606
|
+
chartHeading = doc.createElement('h4'),
|
607
|
+
chartTypes = chart.types || [],
|
608
|
+
// Build axis info - but not for pies and maps. Consider not adding for
|
609
|
+
// certain other types as well (funnel, pyramid?)
|
610
|
+
axesDesc = (
|
611
|
+
chartTypes.length === 1 && chartTypes[0] === 'pie' ||
|
612
|
+
chartTypes[0] === 'map'
|
613
|
+
) && {} || chart.getAxesDescription(),
|
614
|
+
chartTypeInfo = series[0] && typeToSeriesMap[series[0].type] ||
|
615
|
+
typeToSeriesMap['default']; // eslint-disable-line dot-notation
|
756
616
|
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
newSeries.connectEnds ? 2 : 1
|
764
|
-
)];
|
617
|
+
hiddenSection.setAttribute('id', id);
|
618
|
+
hiddenSection.setAttribute('role', 'region');
|
619
|
+
hiddenSection.setAttribute(
|
620
|
+
'aria-label',
|
621
|
+
'Chart screen reader information.'
|
622
|
+
);
|
765
623
|
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
624
|
+
hiddenSection.innerHTML =
|
625
|
+
a11yOptions.screenReaderSectionFormatter &&
|
626
|
+
a11yOptions.screenReaderSectionFormatter(chart) ||
|
627
|
+
'<div>Use regions/landmarks to skip ahead to chart' +
|
628
|
+
(series.length > 1 ? ' and navigate between data series' : '') +
|
629
|
+
'.</div><h3>' +
|
630
|
+
(options.title.text ? htmlencode(options.title.text) : 'Chart') +
|
631
|
+
(
|
632
|
+
options.subtitle && options.subtitle.text ?
|
633
|
+
'. ' + htmlencode(options.subtitle.text) :
|
634
|
+
''
|
635
|
+
) +
|
636
|
+
'</h3><h4>Long description.</h4><div>' +
|
637
|
+
(options.chart.description || 'No description available.') +
|
638
|
+
'</div><h4>Structure.</h4><div>Chart type: ' +
|
639
|
+
(options.chart.typeDescription || chart.getTypeDescription()) +
|
640
|
+
'</div>' +
|
641
|
+
(
|
642
|
+
series.length === 1 ?
|
643
|
+
(
|
644
|
+
'<div>' + chartTypeInfo[0] + ' with ' +
|
645
|
+
series[0].points.length + ' ' +
|
646
|
+
(
|
647
|
+
series[0].points.length === 1 ?
|
648
|
+
chartTypeInfo[1] :
|
649
|
+
chartTypeInfo[2]
|
650
|
+
) +
|
651
|
+
'.</div>'
|
652
|
+
) : ''
|
653
|
+
) +
|
654
|
+
(axesDesc.xAxis ? ('<div>' + axesDesc.xAxis + '</div>') : '') +
|
655
|
+
(axesDesc.yAxis ? ('<div>' + axesDesc.yAxis + '</div>') : '');
|
656
|
+
|
657
|
+
// Add shortcut to data table if export-data is loaded
|
658
|
+
if (chart.getCSV) {
|
659
|
+
tableShortcutAnchor.innerHTML = 'View as data table.';
|
660
|
+
tableShortcutAnchor.href = '#' + tableId;
|
661
|
+
// Make this unreachable by user tabbing
|
662
|
+
tableShortcutAnchor.setAttribute('tabindex', '-1');
|
663
|
+
tableShortcutAnchor.onclick =
|
664
|
+
a11yOptions.onTableAnchorClick || function() {
|
665
|
+
chart.viewData();
|
666
|
+
doc.getElementById(tableId).focus();
|
667
|
+
};
|
668
|
+
tableShortcut.appendChild(tableShortcutAnchor);
|
669
|
+
hiddenSection.appendChild(tableShortcut);
|
670
|
+
}
|
671
|
+
|
672
|
+
// Note: JAWS seems to refuse to read aria-label on the container, so add an
|
673
|
+
// h4 element as title for the chart.
|
674
|
+
chartHeading.innerHTML = 'Chart graphic.';
|
675
|
+
chart.renderTo.insertBefore(chartHeading, chart.renderTo.firstChild);
|
676
|
+
chart.renderTo.insertBefore(hiddenSection, chart.renderTo.firstChild);
|
677
|
+
|
678
|
+
// Hide the section and the chart heading
|
679
|
+
merge(true, chartHeading.style, hiddenStyle);
|
680
|
+
merge(true, hiddenSection.style, hiddenStyle);
|
681
|
+
};
|
682
|
+
|
683
|
+
|
684
|
+
// Make chart container accessible, and wrap table functionality
|
685
|
+
H.Chart.prototype.callbacks.push(function(chart) {
|
686
|
+
var options = chart.options,
|
687
|
+
a11yOptions = options.accessibility;
|
688
|
+
|
689
|
+
if (!a11yOptions.enabled) {
|
690
|
+
return;
|
691
|
+
}
|
692
|
+
|
693
|
+
var titleElement = doc.createElementNS(
|
694
|
+
'http://www.w3.org/2000/svg',
|
695
|
+
'title'
|
696
|
+
),
|
697
|
+
exportGroupElement = doc.createElementNS(
|
698
|
+
'http://www.w3.org/2000/svg',
|
699
|
+
'g'
|
700
|
+
),
|
701
|
+
descElement = chart.container.getElementsByTagName('desc')[0],
|
702
|
+
textElements = chart.container.getElementsByTagName('text'),
|
703
|
+
titleId = 'highcharts-title-' + chart.index,
|
704
|
+
tableId = 'highcharts-data-table-' + chart.index,
|
705
|
+
hiddenSectionId = 'highcharts-information-region-' + chart.index,
|
706
|
+
chartTitle = options.title.text || 'Chart',
|
707
|
+
oldColumnHeaderFormatter = (
|
708
|
+
options.exporting &&
|
709
|
+
options.exporting.csv &&
|
710
|
+
options.exporting.csv.columnHeaderFormatter
|
711
|
+
),
|
712
|
+
topLevelColumns = [];
|
713
|
+
|
714
|
+
// Add SVG title/desc tags
|
715
|
+
titleElement.textContent = htmlencode(chartTitle);
|
716
|
+
titleElement.id = titleId;
|
717
|
+
descElement.parentNode.insertBefore(titleElement, descElement);
|
718
|
+
chart.renderTo.setAttribute('role', 'region');
|
719
|
+
chart.renderTo.setAttribute(
|
720
|
+
'aria-label',
|
721
|
+
stripTags(
|
722
|
+
'Interactive chart. ' + chartTitle +
|
723
|
+
'. Use up and down arrows to navigate with most screen readers.'
|
724
|
+
)
|
725
|
+
);
|
726
|
+
|
727
|
+
// Set screen reader properties on export menu
|
728
|
+
if (
|
729
|
+
chart.exportSVGElements &&
|
730
|
+
chart.exportSVGElements[0] &&
|
731
|
+
chart.exportSVGElements[0].element
|
732
|
+
) {
|
733
|
+
var oldExportCallback = chart.exportSVGElements[0].element.onclick,
|
734
|
+
parent = chart.exportSVGElements[0].element.parentNode;
|
735
|
+
chart.exportSVGElements[0].element.onclick = function() {
|
736
|
+
oldExportCallback.apply(
|
737
|
+
this,
|
738
|
+
Array.prototype.slice.call(arguments)
|
739
|
+
);
|
740
|
+
chart.addAccessibleContextMenuAttribs();
|
741
|
+
chart.highlightExportItem(0);
|
742
|
+
};
|
743
|
+
chart.exportSVGElements[0].element.setAttribute('role', 'button');
|
744
|
+
chart.exportSVGElements[0].element.setAttribute(
|
745
|
+
'aria-label',
|
746
|
+
'View export menu'
|
747
|
+
);
|
748
|
+
exportGroupElement.appendChild(chart.exportSVGElements[0].element);
|
749
|
+
exportGroupElement.setAttribute('role', 'region');
|
750
|
+
exportGroupElement.setAttribute('aria-label', 'Chart export menu');
|
751
|
+
parent.appendChild(exportGroupElement);
|
752
|
+
}
|
753
|
+
|
754
|
+
// Set screen reader properties on input boxes for range selector. We need
|
755
|
+
// to do this regardless of whether or not these are visible, as they are
|
756
|
+
// by default part of the page's tabindex unless we set them to -1.
|
757
|
+
if (chart.rangeSelector) {
|
758
|
+
each(['minInput', 'maxInput'], function(key, i) {
|
759
|
+
if (chart.rangeSelector[key]) {
|
760
|
+
chart.rangeSelector[key].setAttribute('tabindex', '-1');
|
761
|
+
chart.rangeSelector[key].setAttribute('role', 'textbox');
|
762
|
+
chart.rangeSelector[key].setAttribute(
|
763
|
+
'aria-label',
|
764
|
+
'Select ' + (i ? 'end' : 'start') + ' date.'
|
765
|
+
);
|
766
|
+
}
|
767
|
+
});
|
768
|
+
}
|
769
|
+
|
770
|
+
// Hide text elements from screen readers
|
771
|
+
each(textElements, function(el) {
|
772
|
+
el.setAttribute('aria-hidden', 'true');
|
773
|
+
});
|
774
|
+
|
775
|
+
// Add top-secret screen reader region
|
776
|
+
chart.addScreenReaderRegion(hiddenSectionId, tableId);
|
777
|
+
|
778
|
+
|
779
|
+
/* Wrap table functionality from export-data */
|
780
|
+
/* TODO: Can't we just do this in export-data? */
|
781
|
+
|
782
|
+
// Keep track of columns
|
783
|
+
merge(true, options.exporting, {
|
784
|
+
csv: {
|
785
|
+
columnHeaderFormatter: function(item, key, keyLength) {
|
786
|
+
if (!item) {
|
787
|
+
return 'Category';
|
788
|
+
}
|
789
|
+
if (item instanceof H.Axis) {
|
790
|
+
return (item.options.title && item.options.title.text) ||
|
791
|
+
(item.isDatetimeAxis ? 'DateTime' : 'Category');
|
792
|
+
}
|
793
|
+
var prevCol = topLevelColumns[topLevelColumns.length - 1];
|
794
|
+
if (keyLength > 1) {
|
795
|
+
// We need multiple levels of column headers
|
796
|
+
// Populate a list of column headers to add in addition to
|
797
|
+
// the ones added by export-data
|
798
|
+
if ((prevCol && prevCol.text) !== item.name) {
|
799
|
+
topLevelColumns.push({
|
800
|
+
text: item.name,
|
801
|
+
span: keyLength
|
802
|
+
});
|
803
|
+
}
|
804
|
+
}
|
805
|
+
if (oldColumnHeaderFormatter) {
|
806
|
+
return oldColumnHeaderFormatter.call(
|
807
|
+
this,
|
808
|
+
item,
|
809
|
+
key,
|
810
|
+
keyLength
|
811
|
+
);
|
812
|
+
}
|
813
|
+
return keyLength > 1 ? key : item.name;
|
814
|
+
}
|
815
|
+
}
|
816
|
+
});
|
817
|
+
|
818
|
+
// Add ID and title/caption to table HTML
|
819
|
+
H.wrap(chart, 'getTable', function(proceed) {
|
820
|
+
return proceed.apply(this, Array.prototype.slice.call(arguments, 1))
|
821
|
+
.replace(
|
822
|
+
'<table>',
|
823
|
+
'<table id="' + tableId + '" summary="Table representation ' +
|
824
|
+
'of chart"><caption>' + chartTitle + '</caption>'
|
825
|
+
);
|
826
|
+
});
|
827
|
+
|
828
|
+
// Add accessibility attributes and top level columns
|
829
|
+
H.wrap(chart, 'viewData', function(proceed) {
|
830
|
+
if (!this.dataTableDiv) {
|
831
|
+
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
832
|
+
|
833
|
+
var table = doc.getElementById(tableId),
|
834
|
+
head = table.getElementsByTagName('thead')[0],
|
835
|
+
body = table.getElementsByTagName('tbody')[0],
|
836
|
+
firstRow = head.firstChild.children,
|
837
|
+
columnHeaderRow = '<tr><td></td>',
|
838
|
+
cell,
|
839
|
+
newCell;
|
840
|
+
|
841
|
+
// Make table focusable by script
|
842
|
+
table.setAttribute('tabindex', '-1');
|
843
|
+
|
844
|
+
// Create row headers
|
845
|
+
each(body.children, function(el) {
|
846
|
+
cell = el.firstChild;
|
847
|
+
newCell = doc.createElement('th');
|
848
|
+
newCell.setAttribute('scope', 'row');
|
849
|
+
newCell.innerHTML = cell.innerHTML;
|
850
|
+
cell.parentNode.replaceChild(newCell, cell);
|
851
|
+
});
|
852
|
+
|
853
|
+
// Set scope for column headers
|
854
|
+
each(firstRow, function(el) {
|
855
|
+
if (el.tagName === 'TH') {
|
856
|
+
el.setAttribute('scope', 'col');
|
857
|
+
}
|
858
|
+
});
|
859
|
+
|
860
|
+
// Add top level columns
|
861
|
+
if (topLevelColumns.length) {
|
862
|
+
each(topLevelColumns, function(col) {
|
863
|
+
columnHeaderRow += '<th scope="col" colspan="' + col.span +
|
864
|
+
'">' + col.text + '</th>';
|
865
|
+
});
|
866
|
+
head.insertAdjacentHTML('afterbegin', columnHeaderRow);
|
867
|
+
}
|
868
|
+
}
|
869
|
+
});
|
870
|
+
});
|
871
|
+
|
872
|
+
}(Highcharts));
|
873
|
+
(function(H) {
|
874
|
+
/**
|
875
|
+
* Accessibility module - Keyboard navigation
|
876
|
+
*
|
877
|
+
* (c) 2010-2017 Highsoft AS
|
878
|
+
* Author: Oystein Moseng
|
879
|
+
*
|
880
|
+
* License: www.highcharts.com/license
|
881
|
+
*/
|
882
|
+
/* eslint max-len: ["warn", 80, 4] */
|
883
|
+
|
884
|
+
var win = H.win,
|
885
|
+
doc = win.document,
|
886
|
+
each = H.each,
|
887
|
+
addEvent = H.addEvent,
|
888
|
+
fireEvent = H.fireEvent,
|
889
|
+
merge = H.merge,
|
890
|
+
pick = H.pick;
|
891
|
+
|
892
|
+
// Add focus border functionality to SVGElements.
|
893
|
+
// Draws a new rect on top of element around its bounding box.
|
894
|
+
H.extend(H.SVGElement.prototype, {
|
895
|
+
addFocusBorder: function(margin, style) {
|
896
|
+
// Allow updating by just adding new border
|
897
|
+
if (this.focusBorder) {
|
898
|
+
this.removeFocusBorder();
|
899
|
+
}
|
900
|
+
// Add the border rect
|
901
|
+
var bb = this.getBBox(),
|
902
|
+
pad = pick(margin, 3);
|
903
|
+
this.focusBorder = this.renderer.rect(
|
904
|
+
bb.x - pad,
|
905
|
+
bb.y - pad,
|
906
|
+
bb.width + 2 * pad,
|
907
|
+
bb.height + 2 * pad,
|
908
|
+
style && style.borderRadius
|
909
|
+
)
|
910
|
+
.addClass('highcharts-focus-border')
|
911
|
+
|
912
|
+
.attr({
|
913
|
+
stroke: style && style.stroke,
|
914
|
+
'stroke-width': style && style.strokeWidth
|
915
|
+
})
|
916
|
+
|
917
|
+
.attr({
|
918
|
+
zIndex: 99
|
919
|
+
})
|
920
|
+
.add(this.parentGroup);
|
921
|
+
},
|
922
|
+
|
923
|
+
removeFocusBorder: function() {
|
924
|
+
if (this.focusBorder) {
|
925
|
+
this.focusBorder.destroy();
|
926
|
+
delete this.focusBorder;
|
927
|
+
}
|
928
|
+
}
|
929
|
+
});
|
930
|
+
|
931
|
+
|
932
|
+
// Set for which series types it makes sense to move to the closest point with
|
933
|
+
// up/down arrows, and which series types should just move to next series.
|
934
|
+
H.Series.prototype.keyboardMoveVertical = true;
|
935
|
+
each(['column', 'pie'], function(type) {
|
936
|
+
if (H.seriesTypes[type]) {
|
937
|
+
H.seriesTypes[type].prototype.keyboardMoveVertical = false;
|
938
|
+
}
|
939
|
+
});
|
940
|
+
|
941
|
+
/**
|
942
|
+
* Strip HTML tags away from a string. Used for aria-label attributes, painting
|
943
|
+
* on a canvas will fail if the text contains tags.
|
944
|
+
* @param {String} s The input string
|
945
|
+
* @return {String} The filtered string
|
946
|
+
*/
|
947
|
+
function stripTags(s) {
|
948
|
+
return typeof s === 'string' ? s.replace(/<\/?[^>]+(>|$)/g, '') : s;
|
949
|
+
}
|
950
|
+
|
951
|
+
|
952
|
+
H.setOptions({
|
953
|
+
accessibility: {
|
954
|
+
|
955
|
+
/**
|
956
|
+
* Options for keyboard navigation.
|
957
|
+
*
|
958
|
+
* @type {Object}
|
959
|
+
* @since 5.0.0
|
960
|
+
*/
|
961
|
+
keyboardNavigation: {
|
962
|
+
|
963
|
+
/**
|
964
|
+
* Enable keyboard navigation for the chart.
|
965
|
+
*
|
966
|
+
* @type {Boolean}
|
967
|
+
* @default true
|
968
|
+
* @since 5.0.0
|
969
|
+
*/
|
970
|
+
enabled: true,
|
971
|
+
|
972
|
+
/**
|
973
|
+
* Options for the focus border drawn around elements while
|
974
|
+
* navigating through them.
|
975
|
+
*
|
976
|
+
* @since 6.0.3
|
977
|
+
*/
|
978
|
+
focusBorder: {
|
979
|
+
/**
|
980
|
+
* Enable/disable focus border for chart.
|
981
|
+
*/
|
982
|
+
enabled: true,
|
983
|
+
|
984
|
+
/**
|
985
|
+
* Style options for the focus border drawn around elements
|
986
|
+
* while navigating through them. Note that some browsers in
|
987
|
+
* addition draw their own borders for focused elements. These
|
988
|
+
* automatic borders can not be styled by Highcharts.
|
989
|
+
*
|
990
|
+
* In styled mode, the border is given the
|
991
|
+
* `.highcharts-focus-border` class.
|
992
|
+
*/
|
993
|
+
style: {
|
994
|
+
color: '#000000',
|
995
|
+
lineWidth: 1,
|
996
|
+
borderRadius: 2
|
997
|
+
},
|
998
|
+
|
999
|
+
/**
|
1000
|
+
* Focus border margin around the elements.
|
1001
|
+
*/
|
1002
|
+
margin: 2
|
1003
|
+
}
|
1004
|
+
|
1005
|
+
/**
|
1006
|
+
* Skip null points when navigating through points with the
|
1007
|
+
* keyboard.
|
1008
|
+
*
|
1009
|
+
* @type {Boolean}
|
1010
|
+
* @default false
|
1011
|
+
* @since 5.0.0
|
1012
|
+
* @apioption accessibility.keyboardNavigation.skipNullPoints
|
1013
|
+
*/
|
1014
|
+
}
|
1015
|
+
}
|
1016
|
+
});
|
1017
|
+
|
1018
|
+
/**
|
1019
|
+
* Keyboard navigation for the legend. Requires the Accessibility module.
|
1020
|
+
* @since 5.0.14
|
1021
|
+
* @apioption legend.keyboardNavigation
|
1022
|
+
*/
|
1023
|
+
|
1024
|
+
/**
|
1025
|
+
* Enable/disable keyboard navigation for the legend. Requires the Accessibility
|
1026
|
+
* module.
|
1027
|
+
*
|
1028
|
+
* @type {Boolean}
|
1029
|
+
* @see [accessibility.keyboardNavigation](#accessibility.keyboardNavigation.
|
1030
|
+
* enabled)
|
1031
|
+
* @default true
|
1032
|
+
* @since 5.0.13
|
1033
|
+
* @apioption legend.keyboardNavigation.enabled
|
1034
|
+
*/
|
1035
|
+
|
1036
|
+
|
1037
|
+
// Abstraction layer for keyboard navigation. Keep a map of keyCodes to
|
1038
|
+
// handler functions, and a next/prev move handler for tab order. The
|
1039
|
+
// module's keyCode handlers determine when to move to another module.
|
1040
|
+
// Validate holds a function to determine if there are prerequisites for
|
1041
|
+
// this module to run that are not met. Init holds a function to run once
|
1042
|
+
// before any keyCodes are interpreted. Terminate holds a function to run
|
1043
|
+
// once before moving to next/prev module.
|
1044
|
+
// The chart object keeps track of a list of KeyboardNavigationModules.
|
1045
|
+
function KeyboardNavigationModule(chart, options) {
|
1046
|
+
this.chart = chart;
|
1047
|
+
this.id = options.id;
|
1048
|
+
this.keyCodeMap = options.keyCodeMap;
|
1049
|
+
this.validate = options.validate;
|
1050
|
+
this.init = options.init;
|
1051
|
+
this.terminate = options.terminate;
|
1052
|
+
}
|
1053
|
+
KeyboardNavigationModule.prototype = {
|
1054
|
+
// Find handler function(s) for key code in the keyCodeMap and run it.
|
1055
|
+
run: function(e) {
|
1056
|
+
var navModule = this,
|
1057
|
+
keyCode = e.which || e.keyCode,
|
1058
|
+
found = false,
|
1059
|
+
handled = false;
|
1060
|
+
each(this.keyCodeMap, function(codeSet) {
|
1061
|
+
if (codeSet[0].indexOf(keyCode) > -1) {
|
1062
|
+
found = true;
|
1063
|
+
handled = codeSet[1].call(navModule, keyCode, e) === false ?
|
1064
|
+
// If explicitly returning false, we haven't handled it
|
1065
|
+
false :
|
1066
|
+
true;
|
1067
|
+
}
|
1068
|
+
});
|
1069
|
+
// Default tab handler, move to next/prev module
|
1070
|
+
if (!found && keyCode === 9) {
|
1071
|
+
handled = this.move(e.shiftKey ? -1 : 1);
|
1072
|
+
}
|
1073
|
+
return handled;
|
1074
|
+
},
|
1075
|
+
|
1076
|
+
// Move to next/prev valid module, or undefined if none, and init
|
1077
|
+
// it. Returns true on success and false if there is no valid module
|
1078
|
+
// to move to.
|
1079
|
+
move: function(direction) {
|
1080
|
+
var chart = this.chart;
|
1081
|
+
if (this.terminate) {
|
1082
|
+
this.terminate(direction);
|
1083
|
+
}
|
1084
|
+
chart.keyboardNavigationModuleIndex += direction;
|
1085
|
+
var newModule = chart.keyboardNavigationModules[
|
1086
|
+
chart.keyboardNavigationModuleIndex
|
1087
|
+
];
|
1088
|
+
|
1089
|
+
// Remove existing focus border if any
|
1090
|
+
if (chart.focusElement) {
|
1091
|
+
chart.focusElement.removeFocusBorder();
|
1092
|
+
}
|
1093
|
+
|
1094
|
+
// Verify new module
|
1095
|
+
if (newModule) {
|
1096
|
+
if (newModule.validate && !newModule.validate()) {
|
1097
|
+
return this.move(direction); // Invalid module, recurse
|
1098
|
+
}
|
1099
|
+
if (newModule.init) {
|
1100
|
+
newModule.init(direction); // Valid module, init it
|
1101
|
+
return true;
|
1102
|
+
}
|
1103
|
+
}
|
1104
|
+
// No module
|
1105
|
+
chart.keyboardNavigationModuleIndex = 0; // Reset counter
|
1106
|
+
|
1107
|
+
// Set focus to chart or exit anchor depending on direction
|
1108
|
+
if (direction > 0) {
|
1109
|
+
this.chart.exiting = true;
|
1110
|
+
this.chart.tabExitAnchor.focus();
|
1111
|
+
} else {
|
1112
|
+
this.chart.renderTo.focus();
|
1113
|
+
}
|
1114
|
+
|
1115
|
+
return false;
|
1116
|
+
}
|
1117
|
+
};
|
1118
|
+
|
1119
|
+
|
1120
|
+
// Utility function to attempt to fake a click event on an element
|
1121
|
+
function fakeClickEvent(element) {
|
1122
|
+
var fakeEvent;
|
1123
|
+
if (element && element.onclick && doc.createEvent) {
|
1124
|
+
fakeEvent = doc.createEvent('Events');
|
1125
|
+
fakeEvent.initEvent('click', true, false);
|
1126
|
+
element.onclick(fakeEvent);
|
1127
|
+
}
|
1128
|
+
}
|
1129
|
+
|
1130
|
+
|
1131
|
+
// Determine if a point should be skipped
|
1132
|
+
function isSkipPoint(point) {
|
1133
|
+
return point.isNull &&
|
1134
|
+
point.series.chart.options.accessibility
|
1135
|
+
.keyboardNavigation.skipNullPoints ||
|
1136
|
+
point.series.options.skipKeyboardNavigation ||
|
1137
|
+
!point.series.visible;
|
1138
|
+
}
|
1139
|
+
|
1140
|
+
|
1141
|
+
// Get the point in a series that is closest (in distance) to a reference point
|
1142
|
+
// Optionally supply weight factors for x and y directions
|
1143
|
+
function getClosestPoint(point, series, xWeight, yWeight) {
|
1144
|
+
var minDistance = Infinity,
|
1145
|
+
dPoint,
|
1146
|
+
minIx,
|
1147
|
+
distance,
|
1148
|
+
i = series.points.length;
|
1149
|
+
if (point.plotX === undefined || point.plotY === undefined) {
|
1150
|
+
return;
|
1151
|
+
}
|
1152
|
+
while (i--) {
|
1153
|
+
dPoint = series.points[i];
|
1154
|
+
if (dPoint.plotX === undefined || dPoint.plotY === undefined) {
|
1155
|
+
return;
|
1156
|
+
}
|
1157
|
+
distance = (point.plotX - dPoint.plotX) *
|
1158
|
+
(point.plotX - dPoint.plotX) * (xWeight || 1) +
|
1159
|
+
(point.plotY - dPoint.plotY) *
|
1160
|
+
(point.plotY - dPoint.plotY) * (yWeight || 1);
|
1161
|
+
if (distance < minDistance) {
|
1162
|
+
minDistance = distance;
|
1163
|
+
minIx = i;
|
1164
|
+
}
|
1165
|
+
}
|
1166
|
+
return series.points[minIx || 0];
|
1167
|
+
}
|
1168
|
+
|
1169
|
+
|
1170
|
+
// Pan along axis in a direction (1 or -1), optionally with a defined
|
1171
|
+
// granularity (number of steps it takes to walk across current view)
|
1172
|
+
H.Axis.prototype.panStep = function(direction, granularity) {
|
1173
|
+
var gran = granularity || 3,
|
1174
|
+
extremes = this.getExtremes(),
|
1175
|
+
step = (extremes.max - extremes.min) / gran * direction,
|
1176
|
+
newMax = extremes.max + step,
|
1177
|
+
newMin = extremes.min + step,
|
1178
|
+
size = newMax - newMin;
|
1179
|
+
if (direction < 0 && newMin < extremes.dataMin) {
|
1180
|
+
newMin = extremes.dataMin;
|
1181
|
+
newMax = newMin + size;
|
1182
|
+
} else if (direction > 0 && newMax > extremes.dataMax) {
|
1183
|
+
newMax = extremes.dataMax;
|
1184
|
+
newMin = newMax - size;
|
1185
|
+
}
|
1186
|
+
this.setExtremes(newMin, newMax);
|
1187
|
+
};
|
1188
|
+
|
1189
|
+
|
1190
|
+
// Set chart's focus to an SVGElement. Calls focus() on it, and draws the focus
|
1191
|
+
// border. If the focusElement argument is supplied, it draws the border around
|
1192
|
+
// svgElement and sets the focus to focusElement.
|
1193
|
+
H.Chart.prototype.setFocusToElement = function(svgElement, focusElement) {
|
1194
|
+
var focusBorderOptions = this.options.accessibility
|
1195
|
+
.keyboardNavigation.focusBorder;
|
1196
|
+
if (focusBorderOptions.enabled && svgElement !== this.focusElement) {
|
1197
|
+
// Remove old focus border
|
1198
|
+
if (this.focusElement) {
|
1199
|
+
this.focusElement.removeFocusBorder();
|
1200
|
+
}
|
1201
|
+
// Set browser focus if possible
|
1202
|
+
if (
|
1203
|
+
focusElement &&
|
1204
|
+
focusElement.element &&
|
1205
|
+
focusElement.element.focus
|
1206
|
+
) {
|
1207
|
+
focusElement.element.focus();
|
1208
|
+
} else if (svgElement.element.focus) {
|
1209
|
+
svgElement.element.focus();
|
1210
|
+
}
|
1211
|
+
// Draw focus border (since some browsers don't do it automatically)
|
1212
|
+
svgElement.addFocusBorder(focusBorderOptions.margin, {
|
1213
|
+
stroke: focusBorderOptions.style.color,
|
1214
|
+
strokeWidth: focusBorderOptions.style.lineWidth,
|
1215
|
+
borderRadius: focusBorderOptions.style.borderRadius
|
1216
|
+
});
|
1217
|
+
this.focusElement = svgElement;
|
1218
|
+
}
|
1219
|
+
};
|
1220
|
+
|
1221
|
+
|
1222
|
+
// Highlight a point (show tooltip and display hover state). Returns the
|
1223
|
+
// highlighted point.
|
1224
|
+
H.Point.prototype.highlight = function() {
|
1225
|
+
var chart = this.series.chart;
|
1226
|
+
if (!this.isNull) {
|
1227
|
+
this.onMouseOver(); // Show the hover marker and tooltip
|
1228
|
+
} else {
|
1229
|
+
if (chart.tooltip) {
|
1230
|
+
chart.tooltip.hide(0);
|
1231
|
+
}
|
1232
|
+
// Don't call blur on the element, as it messes up the chart div's focus
|
1233
|
+
}
|
1234
|
+
|
1235
|
+
// We focus only after calling onMouseOver because the state change can
|
1236
|
+
// change z-index and mess up the element.
|
1237
|
+
if (this.graphic) {
|
1238
|
+
chart.setFocusToElement(this.graphic);
|
1239
|
+
}
|
1240
|
+
|
1241
|
+
chart.highlightedPoint = this;
|
1242
|
+
return this;
|
1243
|
+
};
|
1244
|
+
|
1245
|
+
|
1246
|
+
// Function to highlight next/previous point in chart
|
1247
|
+
// Returns highlighted point on success, false on failure (no adjacent point to
|
1248
|
+
// highlight in chosen direction)
|
1249
|
+
H.Chart.prototype.highlightAdjacentPoint = function(next) {
|
1250
|
+
var chart = this,
|
1251
|
+
series = chart.series,
|
1252
|
+
curPoint = chart.highlightedPoint,
|
1253
|
+
curPointIndex = curPoint && curPoint.index || 0,
|
1254
|
+
curPoints = curPoint && curPoint.series.points,
|
1255
|
+
lastSeries = chart.series && chart.series[chart.series.length - 1],
|
1256
|
+
lastPoint = lastSeries && lastSeries.points &&
|
1257
|
+
lastSeries.points[lastSeries.points.length - 1],
|
1258
|
+
newSeries,
|
1259
|
+
newPoint,
|
1260
|
+
// Handle connecting ends - where the points array has an extra last
|
1261
|
+
// point that is a reference to the first one. We skip this.
|
1262
|
+
forwardSkipAmount = curPoint && curPoint.series.connectEnds &&
|
1263
|
+
curPointIndex > curPoints.length - 3 ? 2 : 1;
|
1264
|
+
|
1265
|
+
// If no points, return false
|
1266
|
+
if (!series[0] || !series[0].points) {
|
1267
|
+
return false;
|
1268
|
+
}
|
1269
|
+
|
1270
|
+
if (!curPoint) {
|
1271
|
+
// No point is highlighted yet. Try first/last point depending on move
|
1272
|
+
// direction
|
1273
|
+
newPoint = next ? series[0].points[0] : lastPoint;
|
1274
|
+
} else {
|
1275
|
+
// We have a highlighted point.
|
1276
|
+
// Find index of current point in series.points array. Necessary for
|
1277
|
+
// dataGrouping (and maybe zoom?)
|
1278
|
+
if (curPoints[curPointIndex] !== curPoint) {
|
1279
|
+
for (var i = 0; i < curPoints.length; ++i) {
|
1280
|
+
if (curPoints[i] === curPoint) {
|
1281
|
+
curPointIndex = i;
|
1282
|
+
break;
|
1283
|
+
}
|
1284
|
+
}
|
1285
|
+
}
|
1286
|
+
|
1287
|
+
// Grab next/prev point & series
|
1288
|
+
newSeries = series[curPoint.series.index + (next ? 1 : -1)];
|
1289
|
+
newPoint = curPoints[curPointIndex + (next ? forwardSkipAmount : -1)] ||
|
1290
|
+
// Done with this series, try next one
|
1291
|
+
newSeries &&
|
1292
|
+
newSeries.points[next ? 0 : newSeries.points.length - (
|
1293
|
+
newSeries.connectEnds ? 2 : 1
|
1294
|
+
)];
|
1295
|
+
|
1296
|
+
// If there is no adjacent point, we return false
|
1297
|
+
if (newPoint === undefined) {
|
1298
|
+
return false;
|
1299
|
+
}
|
1300
|
+
}
|
771
1301
|
|
772
1302
|
// Recursively skip null points or points in series that should be skipped
|
773
1303
|
if (isSkipPoint(newPoint)) {
|
@@ -779,6 +1309,7 @@
|
|
779
1309
|
return newPoint.highlight();
|
780
1310
|
};
|
781
1311
|
|
1312
|
+
|
782
1313
|
// Highlight first valid point in a series. Returns the point if successfully
|
783
1314
|
// highlighted, otherwise false. If there is a highlighted point in the series,
|
784
1315
|
// use that as starting point.
|
@@ -800,6 +1331,7 @@
|
|
800
1331
|
return false;
|
801
1332
|
};
|
802
1333
|
|
1334
|
+
|
803
1335
|
// Highlight next/previous series in chart. Returns false if no adjacent series
|
804
1336
|
// in the direction, otherwise returns new highlighted point.
|
805
1337
|
H.Chart.prototype.highlightAdjacentSeries = function(down) {
|
@@ -853,6 +1385,7 @@
|
|
853
1385
|
return newPoint.series.highlightFirstValidPoint();
|
854
1386
|
};
|
855
1387
|
|
1388
|
+
|
856
1389
|
// Highlight the closest point vertically
|
857
1390
|
H.Chart.prototype.highlightAdjacentPointVertical = function(down) {
|
858
1391
|
var curPoint = this.highlightedPoint,
|
@@ -896,6 +1429,7 @@
|
|
896
1429
|
return bestPoint ? bestPoint.highlight() : false;
|
897
1430
|
};
|
898
1431
|
|
1432
|
+
|
899
1433
|
// Show the export menu and focus the first item (if exists)
|
900
1434
|
H.Chart.prototype.showExportMenu = function() {
|
901
1435
|
if (this.exportSVGElements && this.exportSVGElements[0]) {
|
@@ -904,6 +1438,26 @@
|
|
904
1438
|
}
|
905
1439
|
};
|
906
1440
|
|
1441
|
+
|
1442
|
+
// Hide export menu
|
1443
|
+
H.Chart.prototype.hideExportMenu = function() {
|
1444
|
+
var exportList = this.exportDivElements;
|
1445
|
+
if (exportList) {
|
1446
|
+
each(exportList, function(el) {
|
1447
|
+
fireEvent(el, 'mouseleave');
|
1448
|
+
});
|
1449
|
+
if (
|
1450
|
+
exportList[this.highlightedExportItem] &&
|
1451
|
+
exportList[this.highlightedExportItem].onmouseout
|
1452
|
+
) {
|
1453
|
+
exportList[this.highlightedExportItem].onmouseout();
|
1454
|
+
}
|
1455
|
+
this.highlightedExportItem = 0;
|
1456
|
+
this.renderTo.focus();
|
1457
|
+
}
|
1458
|
+
};
|
1459
|
+
|
1460
|
+
|
907
1461
|
// Highlight export menu item by index
|
908
1462
|
H.Chart.prototype.highlightExportItem = function(ix) {
|
909
1463
|
var listItem = this.exportDivElements && this.exportDivElements[ix],
|
@@ -930,6 +1484,7 @@
|
|
930
1484
|
}
|
931
1485
|
};
|
932
1486
|
|
1487
|
+
|
933
1488
|
// Highlight range selector button by index
|
934
1489
|
H.Chart.prototype.highlightRangeSelectorButton = function(ix) {
|
935
1490
|
var buttons = this.rangeSelector.buttons;
|
@@ -942,9 +1497,7 @@
|
|
942
1497
|
// Select new
|
943
1498
|
this.highlightedRangeSelectorItemIx = ix;
|
944
1499
|
if (buttons[ix]) {
|
945
|
-
|
946
|
-
buttons[ix].element.focus();
|
947
|
-
}
|
1500
|
+
this.setFocusToElement(buttons[ix].box, buttons[ix]);
|
948
1501
|
this.oldRangeSelectorItemState = buttons[ix].state;
|
949
1502
|
buttons[ix].setState(2);
|
950
1503
|
return true;
|
@@ -952,146 +1505,39 @@
|
|
952
1505
|
return false;
|
953
1506
|
};
|
954
1507
|
|
1508
|
+
|
955
1509
|
// Highlight legend item by index
|
956
1510
|
H.Chart.prototype.highlightLegendItem = function(ix) {
|
957
|
-
var items = this.legend.allItems
|
958
|
-
|
959
|
-
fireEvent(
|
960
|
-
items[this.highlightedLegendItemIx].legendGroup.element,
|
961
|
-
'mouseout'
|
962
|
-
);
|
963
|
-
}
|
964
|
-
this.highlightedLegendItemIx = ix;
|
1511
|
+
var items = this.legend.allItems,
|
1512
|
+
oldIx = this.highlightedLegendItemIx;
|
965
1513
|
if (items[ix]) {
|
966
|
-
if (items[
|
967
|
-
|
1514
|
+
if (items[oldIx]) {
|
1515
|
+
fireEvent(
|
1516
|
+
items[oldIx].legendGroup.element,
|
1517
|
+
'mouseout'
|
1518
|
+
);
|
968
1519
|
}
|
1520
|
+
this.highlightedLegendItemIx = ix;
|
1521
|
+
this.setFocusToElement(items[ix].legendItem, items[ix].legendGroup);
|
969
1522
|
fireEvent(items[ix].legendGroup.element, 'mouseover');
|
970
1523
|
return true;
|
971
1524
|
}
|
972
1525
|
return false;
|
973
1526
|
};
|
974
1527
|
|
975
|
-
// Hide export menu
|
976
|
-
H.Chart.prototype.hideExportMenu = function() {
|
977
|
-
var exportList = this.exportDivElements;
|
978
|
-
if (exportList) {
|
979
|
-
each(exportList, function(el) {
|
980
|
-
fireEvent(el, 'mouseleave');
|
981
|
-
});
|
982
|
-
if (
|
983
|
-
exportList[this.highlightedExportItem] &&
|
984
|
-
exportList[this.highlightedExportItem].onmouseout
|
985
|
-
) {
|
986
|
-
exportList[this.highlightedExportItem].onmouseout();
|
987
|
-
}
|
988
|
-
this.highlightedExportItem = 0;
|
989
|
-
this.renderTo.focus();
|
990
|
-
}
|
991
|
-
};
|
992
1528
|
|
993
|
-
// Add keyboard navigation handling to chart
|
994
|
-
H.Chart.prototype.
|
1529
|
+
// Add keyboard navigation handling modules to chart
|
1530
|
+
H.Chart.prototype.addKeyboardNavigationModules = function() {
|
995
1531
|
var chart = this;
|
996
1532
|
|
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.
|
1004
|
-
function KeyboardNavigationModule(options) {
|
1005
|
-
this.id = options.id;
|
1006
|
-
this.keyCodeMap = options.keyCodeMap;
|
1007
|
-
this.move = options.move;
|
1008
|
-
this.validate = options.validate;
|
1009
|
-
this.init = options.init;
|
1010
|
-
this.terminate = options.terminate;
|
1011
|
-
}
|
1012
|
-
KeyboardNavigationModule.prototype = {
|
1013
|
-
// Find handler function(s) for key code in the keyCodeMap and run it.
|
1014
|
-
run: function(e) {
|
1015
|
-
var navModule = this,
|
1016
|
-
keyCode = e.which || e.keyCode,
|
1017
|
-
found = false,
|
1018
|
-
handled = false;
|
1019
|
-
each(this.keyCodeMap, function(codeSet) {
|
1020
|
-
if (codeSet[0].indexOf(keyCode) > -1) {
|
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;
|
1026
|
-
}
|
1027
|
-
});
|
1028
|
-
// Default tab handler, move to next/prev module
|
1029
|
-
if (!found && keyCode === 9) {
|
1030
|
-
handled = this.move(e.shiftKey ? -1 : 1);
|
1031
|
-
}
|
1032
|
-
return handled;
|
1033
|
-
}
|
1034
|
-
};
|
1035
|
-
// Maintain abstraction between KeyboardNavigationModule and Highcharts.
|
1036
|
-
// The chart object keeps track of a list of KeyboardNavigationModules that
|
1037
|
-
// we move through.
|
1038
1533
|
function navModuleFactory(id, keyMap, options) {
|
1039
|
-
return new KeyboardNavigationModule(merge({
|
1040
|
-
keyCodeMap: keyMap
|
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.
|
1044
|
-
move: function(direction) {
|
1045
|
-
if (this.terminate) {
|
1046
|
-
this.terminate(direction);
|
1047
|
-
}
|
1048
|
-
chart.keyboardNavigationModuleIndex += direction;
|
1049
|
-
var newModule = chart.keyboardNavigationModules[
|
1050
|
-
chart.keyboardNavigationModuleIndex
|
1051
|
-
];
|
1052
|
-
if (newModule) {
|
1053
|
-
if (newModule.validate && !newModule.validate()) {
|
1054
|
-
return this.move(direction); // Invalid module
|
1055
|
-
}
|
1056
|
-
if (newModule.init) {
|
1057
|
-
newModule.init(direction); // Valid module, init it
|
1058
|
-
return true;
|
1059
|
-
}
|
1060
|
-
}
|
1061
|
-
// No module
|
1062
|
-
chart.keyboardNavigationModuleIndex = 0; // Reset counter
|
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
|
-
|
1071
|
-
return false;
|
1072
|
-
}
|
1534
|
+
return new KeyboardNavigationModule(chart, merge({
|
1535
|
+
keyCodeMap: keyMap
|
1073
1536
|
}, {
|
1074
1537
|
id: id
|
1075
1538
|
}, options));
|
1076
1539
|
}
|
1077
1540
|
|
1078
|
-
// Route keydown events
|
1079
|
-
function keydownHandler(ev) {
|
1080
|
-
var e = ev || win.event,
|
1081
|
-
curNavModule = chart.keyboardNavigationModules[
|
1082
|
-
chart.keyboardNavigationModuleIndex
|
1083
|
-
];
|
1084
|
-
|
1085
|
-
// If there is a navigation module for the current index, run it.
|
1086
|
-
// Otherwise, we are outside of the chart in some direction.
|
1087
|
-
if (curNavModule) {
|
1088
|
-
if (curNavModule.run(e)) {
|
1089
|
-
// Successfully handled this key event, stop default handling
|
1090
|
-
e.preventDefault();
|
1091
|
-
}
|
1092
|
-
}
|
1093
|
-
}
|
1094
|
-
|
1095
1541
|
// List of the different keyboard handling modes we use depending on where
|
1096
1542
|
// we are in the chart. Each mode has a set of handling functions mapped to
|
1097
1543
|
// key codes. Each mode determines when to move to the next/prev mode.
|
@@ -1240,550 +1686,352 @@
|
|
1240
1686
|
chart.hideExportMenu();
|
1241
1687
|
}
|
1242
1688
|
}),
|
1243
|
-
|
1244
|
-
// Map zoom
|
1245
|
-
navModuleFactory('mapZoom', [
|
1246
|
-
// Up/down/left/right
|
1247
|
-
[
|
1248
|
-
[38, 40, 37, 39],
|
1249
|
-
function(keyCode) {
|
1250
|
-
chart[keyCode === 38 || keyCode === 40 ? 'yAxis' : 'xAxis'][0]
|
1251
|
-
.panStep(keyCode < 39 ? -1 : 1);
|
1252
|
-
}
|
1253
|
-
],
|
1254
|
-
|
1255
|
-
// Tabs
|
1256
|
-
[
|
1257
|
-
[9],
|
1258
|
-
function(keyCode, e) {
|
1259
|
-
var button;
|
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?
|
1266
|
-
chart.mapZoom(); // Reset zoom
|
1267
|
-
// Nowhere to go, go to prev/next module
|
1268
|
-
return this.move(e.shiftKey ? -1 : 1);
|
1269
|
-
}
|
1270
|
-
chart.focusedMapNavButtonIx += e.shiftKey ? -1 : 1;
|
1271
|
-
button = chart.mapNavButtons[chart.focusedMapNavButtonIx];
|
1272
|
-
if (button.element.focus) {
|
1273
|
-
button.element.focus();
|
1274
|
-
}
|
1275
|
-
button.setState(2);
|
1276
|
-
}
|
1277
|
-
],
|
1278
|
-
|
1279
|
-
// Enter/Spacebar
|
1280
|
-
[
|
1281
|
-
[13, 32],
|
1282
|
-
function() {
|
1283
|
-
fakeClickEvent(
|
1284
|
-
chart.mapNavButtons[chart.focusedMapNavButtonIx].element
|
1285
|
-
);
|
1286
|
-
}
|
1287
|
-
]
|
1288
|
-
], {
|
1289
|
-
// Only run this module if we have map zoom on the chart
|
1290
|
-
validate: function() {
|
1291
|
-
return (
|
1292
|
-
chart.mapZoom &&
|
1293
|
-
chart.mapNavButtons &&
|
1294
|
-
chart.mapNavButtons.length === 2
|
1295
|
-
);
|
1296
|
-
},
|
1297
|
-
|
1298
|
-
// Make zoom buttons do their magic
|
1299
|
-
init: function(direction) {
|
1300
|
-
var zoomIn = chart.mapNavButtons[0],
|
1301
|
-
zoomOut = chart.mapNavButtons[1],
|
1302
|
-
initialButton = direction > 0 ? zoomIn : zoomOut;
|
1303
|
-
|
1304
|
-
each(chart.mapNavButtons, function(button, i) {
|
1305
|
-
button.element.setAttribute('tabindex', -1);
|
1306
|
-
button.element.setAttribute('role', 'button');
|
1307
|
-
button.element.setAttribute(
|
1308
|
-
'aria-label',
|
1309
|
-
'Zoom ' + (i ? 'out' : '') + 'chart'
|
1310
|
-
);
|
1311
|
-
});
|
1312
|
-
|
1313
|
-
if (initialButton.element.focus) {
|
1314
|
-
initialButton.element.focus();
|
1315
|
-
}
|
1316
|
-
initialButton.setState(2);
|
1317
|
-
chart.focusedMapNavButtonIx = direction > 0 ? 0 : 1;
|
1318
|
-
}
|
1319
|
-
}),
|
1320
|
-
|
1321
|
-
// Highstock range selector (minus input boxes)
|
1322
|
-
navModuleFactory('rangeSelector', [
|
1323
|
-
// Left/Right/Up/Down
|
1324
|
-
[
|
1325
|
-
[37, 39, 38, 40],
|
1326
|
-
function(keyCode) {
|
1327
|
-
var direction = (keyCode === 37 || keyCode === 38) ? -1 : 1;
|
1328
|
-
// Try to highlight next/prev button
|
1329
|
-
if (!chart.highlightRangeSelectorButton(
|
1330
|
-
chart.highlightedRangeSelectorItemIx + direction
|
1331
|
-
)) {
|
1332
|
-
return this.move(direction);
|
1333
|
-
}
|
1334
|
-
}
|
1335
|
-
],
|
1336
|
-
// Enter/Spacebar
|
1337
|
-
[
|
1338
|
-
[13, 32],
|
1339
|
-
function() {
|
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
|
-
);
|
1347
|
-
}
|
1348
|
-
}
|
1349
|
-
]
|
1350
|
-
], {
|
1351
|
-
// Only run this module if we have range selector
|
1352
|
-
validate: function() {
|
1353
|
-
return (
|
1354
|
-
chart.rangeSelector &&
|
1355
|
-
chart.rangeSelector.buttons &&
|
1356
|
-
chart.rangeSelector.buttons.length
|
1357
|
-
);
|
1358
|
-
},
|
1359
|
-
|
1360
|
-
// Make elements focusable and accessible
|
1361
|
-
init: function(direction) {
|
1362
|
-
each(chart.rangeSelector.buttons, function(button) {
|
1363
|
-
button.element.setAttribute('tabindex', '-1');
|
1364
|
-
button.element.setAttribute('role', 'button');
|
1365
|
-
button.element.setAttribute(
|
1366
|
-
'aria-label',
|
1367
|
-
'Select range ' + (button.text && button.text.textStr)
|
1368
|
-
);
|
1369
|
-
});
|
1370
|
-
// Focus first/last button
|
1371
|
-
chart.highlightRangeSelectorButton(
|
1372
|
-
direction > 0 ? 0 : chart.rangeSelector.buttons.length - 1
|
1373
|
-
);
|
1374
|
-
}
|
1375
|
-
}),
|
1376
|
-
|
1377
|
-
// Highstock range selector, input boxes
|
1378
|
-
navModuleFactory('rangeSelectorInput', [
|
1379
|
-
// Tab/Up/Down
|
1380
|
-
[
|
1381
|
-
[9, 38, 40],
|
1382
|
-
function(keyCode, e) {
|
1383
|
-
var direction =
|
1384
|
-
(keyCode === 9 && e.shiftKey || keyCode === 38) ? -1 : 1,
|
1385
|
-
|
1386
|
-
newIx = chart.highlightedInputRangeIx =
|
1387
|
-
chart.highlightedInputRangeIx + direction;
|
1388
|
-
|
1389
|
-
// Try to highlight next/prev item in list.
|
1390
|
-
if (newIx > 1 || newIx < 0) { // Out of range
|
1391
|
-
return this.move(direction);
|
1392
|
-
}
|
1393
|
-
chart.rangeSelector[newIx ? 'maxInput' : 'minInput'].focus();
|
1394
|
-
}
|
1395
|
-
]
|
1396
|
-
], {
|
1397
|
-
// Only run if we have range selector with input boxes
|
1398
|
-
validate: function() {
|
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
|
-
);
|
1411
|
-
},
|
1412
|
-
|
1413
|
-
// Highlight first/last input box
|
1414
|
-
init: function(direction) {
|
1415
|
-
chart.highlightedInputRangeIx = direction > 0 ? 0 : 1;
|
1416
|
-
chart.rangeSelector[
|
1417
|
-
chart.highlightedInputRangeIx ? 'maxInput' : 'minInput'
|
1418
|
-
].focus();
|
1419
|
-
}
|
1420
|
-
}),
|
1421
|
-
|
1422
|
-
// Legend navigation
|
1423
|
-
navModuleFactory('legend', [
|
1424
|
-
// Left/Right/Up/Down
|
1689
|
+
|
1690
|
+
// Map zoom
|
1691
|
+
navModuleFactory('mapZoom', [
|
1692
|
+
// Up/down/left/right
|
1425
1693
|
[
|
1426
|
-
[
|
1694
|
+
[38, 40, 37, 39],
|
1427
1695
|
function(keyCode) {
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1696
|
+
chart[keyCode === 38 || keyCode === 40 ? 'yAxis' : 'xAxis'][0]
|
1697
|
+
.panStep(keyCode < 39 ? -1 : 1);
|
1698
|
+
}
|
1699
|
+
],
|
1700
|
+
|
1701
|
+
// Tabs
|
1702
|
+
[
|
1703
|
+
[9],
|
1704
|
+
function(keyCode, e) {
|
1705
|
+
var button;
|
1706
|
+
// Deselect old
|
1707
|
+
chart.mapNavButtons[chart.focusedMapNavButtonIx].setState(0);
|
1708
|
+
if (
|
1709
|
+
e.shiftKey && !chart.focusedMapNavButtonIx ||
|
1710
|
+
!e.shiftKey && chart.focusedMapNavButtonIx
|
1711
|
+
) { // trying to go somewhere we can't?
|
1712
|
+
chart.mapZoom(); // Reset zoom
|
1713
|
+
// Nowhere to go, go to prev/next module
|
1714
|
+
return this.move(e.shiftKey ? -1 : 1);
|
1434
1715
|
}
|
1716
|
+
chart.focusedMapNavButtonIx += e.shiftKey ? -1 : 1;
|
1717
|
+
button = chart.mapNavButtons[chart.focusedMapNavButtonIx];
|
1718
|
+
chart.setFocusToElement(button.box, button);
|
1719
|
+
button.setState(2);
|
1435
1720
|
}
|
1436
1721
|
],
|
1722
|
+
|
1437
1723
|
// Enter/Spacebar
|
1438
1724
|
[
|
1439
1725
|
[13, 32],
|
1440
1726
|
function() {
|
1441
1727
|
fakeClickEvent(
|
1442
|
-
chart.
|
1443
|
-
chart.highlightedLegendItemIx
|
1444
|
-
].legendItem.element.parentNode
|
1728
|
+
chart.mapNavButtons[chart.focusedMapNavButtonIx].element
|
1445
1729
|
);
|
1446
1730
|
}
|
1447
1731
|
]
|
1448
1732
|
], {
|
1449
|
-
// Only run this module if we have
|
1450
|
-
// it - item. Don't run if the legend is populated by a colorAxis.
|
1451
|
-
// Don't run if legend navigation is disabled.
|
1733
|
+
// Only run this module if we have map zoom on the chart
|
1452
1734
|
validate: function() {
|
1453
|
-
return
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1735
|
+
return (
|
1736
|
+
chart.mapZoom &&
|
1737
|
+
chart.mapNavButtons &&
|
1738
|
+
chart.mapNavButtons.length === 2
|
1739
|
+
);
|
1458
1740
|
},
|
1459
1741
|
|
1460
|
-
// Make
|
1742
|
+
// Make zoom buttons do their magic
|
1461
1743
|
init: function(direction) {
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1744
|
+
var zoomIn = chart.mapNavButtons[0],
|
1745
|
+
zoomOut = chart.mapNavButtons[1],
|
1746
|
+
initialButton = direction > 0 ? zoomIn : zoomOut;
|
1747
|
+
|
1748
|
+
each(chart.mapNavButtons, function(button, i) {
|
1749
|
+
button.element.setAttribute('tabindex', -1);
|
1750
|
+
button.element.setAttribute('role', 'button');
|
1751
|
+
button.element.setAttribute(
|
1466
1752
|
'aria-label',
|
1467
|
-
'
|
1753
|
+
'Zoom ' + (i ? 'out ' : '') + 'chart'
|
1468
1754
|
);
|
1469
1755
|
});
|
1470
|
-
// Focus first/last item
|
1471
|
-
chart.highlightLegendItem(
|
1472
|
-
direction > 0 ? 0 : chart.legend.allItems.length - 1
|
1473
|
-
);
|
1474
|
-
}
|
1475
|
-
})
|
1476
|
-
];
|
1477
|
-
|
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.
|
1481
|
-
chart.keyboardNavigationModuleIndex = 0;
|
1482
|
-
|
1483
|
-
// Make chart reachable by tab
|
1484
|
-
if (
|
1485
|
-
chart.container.hasAttribute &&
|
1486
|
-
!chart.container.hasAttribute('tabIndex')
|
1487
|
-
) {
|
1488
|
-
chart.container.setAttribute('tabindex', '0');
|
1489
|
-
}
|
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
|
-
|
1502
|
-
// Handle keyboard events
|
1503
|
-
addEvent(chart.renderTo, 'keydown', keydownHandler);
|
1504
|
-
addEvent(chart, 'destroy', function() {
|
1505
|
-
if (chart.renderTo) {
|
1506
|
-
removeEvent(chart.renderTo, 'keydown', keydownHandler);
|
1507
|
-
}
|
1508
|
-
});
|
1509
|
-
};
|
1510
|
-
|
1511
|
-
// Add screen reader region to chart.
|
1512
|
-
// tableId is the HTML id of the table to focus when clicking the table anchor
|
1513
|
-
// in the screen reader region.
|
1514
|
-
H.Chart.prototype.addScreenReaderRegion = function(id, tableId) {
|
1515
|
-
var chart = this,
|
1516
|
-
series = chart.series,
|
1517
|
-
options = chart.options,
|
1518
|
-
a11yOptions = options.accessibility,
|
1519
|
-
hiddenSection = chart.screenReaderRegion = doc.createElement('div'),
|
1520
|
-
tableShortcut = doc.createElement('h4'),
|
1521
|
-
tableShortcutAnchor = doc.createElement('a'),
|
1522
|
-
chartHeading = doc.createElement('h4'),
|
1523
|
-
chartTypes = chart.types || [],
|
1524
|
-
// Build axis info - but not for pies and maps. Consider not adding for
|
1525
|
-
// certain other types as well (funnel, pyramid?)
|
1526
|
-
axesDesc = (
|
1527
|
-
chartTypes.length === 1 && chartTypes[0] === 'pie' ||
|
1528
|
-
chartTypes[0] === 'map'
|
1529
|
-
) && {} || chart.getAxesDescription(),
|
1530
|
-
chartTypeInfo = series[0] && typeToSeriesMap[series[0].type] ||
|
1531
|
-
typeToSeriesMap['default']; // eslint-disable-line dot-notation
|
1532
|
-
|
1533
|
-
hiddenSection.setAttribute('id', id);
|
1534
|
-
hiddenSection.setAttribute('role', 'region');
|
1535
|
-
hiddenSection.setAttribute(
|
1536
|
-
'aria-label',
|
1537
|
-
'Chart screen reader information.'
|
1538
|
-
);
|
1539
|
-
|
1540
|
-
hiddenSection.innerHTML =
|
1541
|
-
a11yOptions.screenReaderSectionFormatter &&
|
1542
|
-
a11yOptions.screenReaderSectionFormatter(chart) ||
|
1543
|
-
'<div>Use regions/landmarks to skip ahead to chart' +
|
1544
|
-
(series.length > 1 ? ' and navigate between data series' : '') +
|
1545
|
-
'.</div><h3>' +
|
1546
|
-
(options.title.text ? htmlencode(options.title.text) : 'Chart') +
|
1547
|
-
(
|
1548
|
-
options.subtitle && options.subtitle.text ?
|
1549
|
-
'. ' + htmlencode(options.subtitle.text) :
|
1550
|
-
''
|
1551
|
-
) +
|
1552
|
-
'</h3><h4>Long description.</h4><div>' +
|
1553
|
-
(options.chart.description || 'No description available.') +
|
1554
|
-
'</div><h4>Structure.</h4><div>Chart type: ' +
|
1555
|
-
(options.chart.typeDescription || chart.getTypeDescription()) +
|
1556
|
-
'</div>' +
|
1557
|
-
(
|
1558
|
-
series.length === 1 ?
|
1559
|
-
(
|
1560
|
-
'<div>' + chartTypeInfo[0] + ' with ' +
|
1561
|
-
series[0].points.length + ' ' +
|
1562
|
-
(
|
1563
|
-
series[0].points.length === 1 ?
|
1564
|
-
chartTypeInfo[1] :
|
1565
|
-
chartTypeInfo[2]
|
1566
|
-
) +
|
1567
|
-
'.</div>'
|
1568
|
-
) : ''
|
1569
|
-
) +
|
1570
|
-
(axesDesc.xAxis ? ('<div>' + axesDesc.xAxis + '</div>') : '') +
|
1571
|
-
(axesDesc.yAxis ? ('<div>' + axesDesc.yAxis + '</div>') : '');
|
1572
|
-
|
1573
|
-
// Add shortcut to data table if export-data is loaded
|
1574
|
-
if (chart.getCSV) {
|
1575
|
-
tableShortcutAnchor.innerHTML = 'View as data table.';
|
1576
|
-
tableShortcutAnchor.href = '#' + tableId;
|
1577
|
-
// Make this unreachable by user tabbing
|
1578
|
-
tableShortcutAnchor.setAttribute('tabindex', '-1');
|
1579
|
-
tableShortcutAnchor.onclick =
|
1580
|
-
a11yOptions.onTableAnchorClick || function() {
|
1581
|
-
chart.viewData();
|
1582
|
-
doc.getElementById(tableId).focus();
|
1583
|
-
};
|
1584
|
-
tableShortcut.appendChild(tableShortcutAnchor);
|
1585
|
-
hiddenSection.appendChild(tableShortcut);
|
1586
|
-
}
|
1587
|
-
|
1588
|
-
// Note: JAWS seems to refuse to read aria-label on the container, so add an
|
1589
|
-
// h4 element as title for the chart.
|
1590
|
-
chartHeading.innerHTML = 'Chart graphic.';
|
1591
|
-
chart.renderTo.insertBefore(chartHeading, chart.renderTo.firstChild);
|
1592
|
-
chart.renderTo.insertBefore(hiddenSection, chart.renderTo.firstChild);
|
1593
|
-
|
1594
|
-
// Hide the section and the chart heading
|
1595
|
-
merge(true, chartHeading.style, hiddenStyle);
|
1596
|
-
merge(true, hiddenSection.style, hiddenStyle);
|
1597
|
-
};
|
1598
|
-
|
1599
|
-
|
1600
|
-
// Make chart container accessible, and wrap table functionality
|
1601
|
-
H.Chart.prototype.callbacks.push(function(chart) {
|
1602
|
-
var options = chart.options,
|
1603
|
-
a11yOptions = options.accessibility;
|
1604
|
-
|
1605
|
-
if (!a11yOptions.enabled) {
|
1606
|
-
return;
|
1607
|
-
}
|
1608
|
-
|
1609
|
-
var titleElement = doc.createElementNS(
|
1610
|
-
'http://www.w3.org/2000/svg',
|
1611
|
-
'title'
|
1612
|
-
),
|
1613
|
-
exportGroupElement = doc.createElementNS(
|
1614
|
-
'http://www.w3.org/2000/svg',
|
1615
|
-
'g'
|
1616
|
-
),
|
1617
|
-
descElement = chart.container.getElementsByTagName('desc')[0],
|
1618
|
-
textElements = chart.container.getElementsByTagName('text'),
|
1619
|
-
titleId = 'highcharts-title-' + chart.index,
|
1620
|
-
tableId = 'highcharts-data-table-' + chart.index,
|
1621
|
-
hiddenSectionId = 'highcharts-information-region-' + chart.index,
|
1622
|
-
chartTitle = options.title.text || 'Chart',
|
1623
|
-
oldColumnHeaderFormatter = (
|
1624
|
-
options.exporting &&
|
1625
|
-
options.exporting.csv &&
|
1626
|
-
options.exporting.csv.columnHeaderFormatter
|
1627
|
-
),
|
1628
|
-
topLevelColumns = [];
|
1629
1756
|
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
chart.renderTo.setAttribute(
|
1636
|
-
'aria-label',
|
1637
|
-
'Interactive chart. ' + chartTitle +
|
1638
|
-
'. Use up and down arrows to navigate with most screen readers.'
|
1639
|
-
);
|
1757
|
+
chart.setFocusToElement(initialButton.box, initialButton);
|
1758
|
+
initialButton.setState(2);
|
1759
|
+
chart.focusedMapNavButtonIx = direction > 0 ? 0 : 1;
|
1760
|
+
}
|
1761
|
+
}),
|
1640
1762
|
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1763
|
+
// Highstock range selector (minus input boxes)
|
1764
|
+
navModuleFactory('rangeSelector', [
|
1765
|
+
// Left/Right/Up/Down
|
1766
|
+
[
|
1767
|
+
[37, 39, 38, 40],
|
1768
|
+
function(keyCode) {
|
1769
|
+
var direction = (keyCode === 37 || keyCode === 38) ? -1 : 1;
|
1770
|
+
// Try to highlight next/prev button
|
1771
|
+
if (!chart.highlightRangeSelectorButton(
|
1772
|
+
chart.highlightedRangeSelectorItemIx + direction
|
1773
|
+
)) {
|
1774
|
+
return this.move(direction);
|
1775
|
+
}
|
1776
|
+
}
|
1777
|
+
],
|
1778
|
+
// Enter/Spacebar
|
1779
|
+
[
|
1780
|
+
[13, 32],
|
1781
|
+
function() {
|
1782
|
+
// Don't allow click if button used to be disabled
|
1783
|
+
if (chart.oldRangeSelectorItemState !== 3) {
|
1784
|
+
fakeClickEvent(
|
1785
|
+
chart.rangeSelector.buttons[
|
1786
|
+
chart.highlightedRangeSelectorItemIx
|
1787
|
+
].element
|
1788
|
+
);
|
1789
|
+
}
|
1790
|
+
}
|
1791
|
+
]
|
1792
|
+
], {
|
1793
|
+
// Only run this module if we have range selector
|
1794
|
+
validate: function() {
|
1795
|
+
return (
|
1796
|
+
chart.rangeSelector &&
|
1797
|
+
chart.rangeSelector.buttons &&
|
1798
|
+
chart.rangeSelector.buttons.length
|
1799
|
+
);
|
1800
|
+
},
|
1667
1801
|
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1802
|
+
// Make elements focusable and accessible
|
1803
|
+
init: function(direction) {
|
1804
|
+
each(chart.rangeSelector.buttons, function(button) {
|
1805
|
+
button.element.setAttribute('tabindex', '-1');
|
1806
|
+
button.element.setAttribute('role', 'button');
|
1807
|
+
button.element.setAttribute(
|
1808
|
+
'aria-label',
|
1809
|
+
'Select range ' + (button.text && button.text.textStr)
|
1810
|
+
);
|
1811
|
+
});
|
1812
|
+
// Focus first/last button
|
1813
|
+
chart.highlightRangeSelectorButton(
|
1814
|
+
direction > 0 ? 0 : chart.rangeSelector.buttons.length - 1
|
1679
1815
|
);
|
1680
1816
|
}
|
1681
|
-
})
|
1682
|
-
}
|
1817
|
+
}),
|
1683
1818
|
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1819
|
+
// Highstock range selector, input boxes
|
1820
|
+
navModuleFactory('rangeSelectorInput', [
|
1821
|
+
// Tab/Up/Down
|
1822
|
+
[
|
1823
|
+
[9, 38, 40],
|
1824
|
+
function(keyCode, e) {
|
1825
|
+
var direction =
|
1826
|
+
(keyCode === 9 && e.shiftKey || keyCode === 38) ? -1 : 1,
|
1688
1827
|
|
1689
|
-
|
1690
|
-
|
1828
|
+
newIx = chart.highlightedInputRangeIx =
|
1829
|
+
chart.highlightedInputRangeIx + direction;
|
1691
1830
|
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1831
|
+
// Try to highlight next/prev item in list.
|
1832
|
+
if (newIx > 1 || newIx < 0) { // Out of range
|
1833
|
+
return this.move(direction);
|
1834
|
+
}
|
1835
|
+
chart.rangeSelector[newIx ? 'maxInput' : 'minInput'].focus();
|
1836
|
+
}
|
1837
|
+
]
|
1838
|
+
], {
|
1839
|
+
// Only run if we have range selector with input boxes
|
1840
|
+
validate: function() {
|
1841
|
+
var inputVisible = (
|
1842
|
+
chart.rangeSelector &&
|
1843
|
+
chart.rangeSelector.inputGroup &&
|
1844
|
+
chart.rangeSelector.inputGroup.element
|
1845
|
+
.getAttribute('visibility') !== 'hidden'
|
1846
|
+
);
|
1847
|
+
return (
|
1848
|
+
inputVisible &&
|
1849
|
+
chart.options.rangeSelector.inputEnabled !== false &&
|
1850
|
+
chart.rangeSelector.minInput &&
|
1851
|
+
chart.rangeSelector.maxInput
|
1852
|
+
);
|
1853
|
+
},
|
1696
1854
|
|
1697
|
-
|
1855
|
+
// Highlight first/last input box
|
1856
|
+
init: function(direction) {
|
1857
|
+
chart.highlightedInputRangeIx = direction > 0 ? 0 : 1;
|
1858
|
+
chart.rangeSelector[
|
1859
|
+
chart.highlightedInputRangeIx ? 'maxInput' : 'minInput'
|
1860
|
+
].focus();
|
1861
|
+
}
|
1862
|
+
}),
|
1698
1863
|
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
if (keyLength > 1) {
|
1712
|
-
// We need multiple levels of column headers
|
1713
|
-
// Populate a list of column headers to add in addition to
|
1714
|
-
// the ones added by export-data
|
1715
|
-
if ((prevCol && prevCol.text) !== item.name) {
|
1716
|
-
topLevelColumns.push({
|
1717
|
-
text: item.name,
|
1718
|
-
span: keyLength
|
1719
|
-
});
|
1864
|
+
// Legend navigation
|
1865
|
+
navModuleFactory('legend', [
|
1866
|
+
// Left/Right/Up/Down
|
1867
|
+
[
|
1868
|
+
[37, 39, 38, 40],
|
1869
|
+
function(keyCode) {
|
1870
|
+
var direction = (keyCode === 37 || keyCode === 38) ? -1 : 1;
|
1871
|
+
// Try to highlight next/prev legend item
|
1872
|
+
if (!chart.highlightLegendItem(
|
1873
|
+
chart.highlightedLegendItemIx + direction
|
1874
|
+
)) {
|
1875
|
+
return this.move(direction);
|
1720
1876
|
}
|
1721
1877
|
}
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1878
|
+
],
|
1879
|
+
// Enter/Spacebar
|
1880
|
+
[
|
1881
|
+
[13, 32],
|
1882
|
+
function() {
|
1883
|
+
fakeClickEvent(
|
1884
|
+
chart.legend.allItems[
|
1885
|
+
chart.highlightedLegendItemIx
|
1886
|
+
].legendItem.element.parentNode
|
1728
1887
|
);
|
1729
1888
|
}
|
1730
|
-
|
1889
|
+
]
|
1890
|
+
], {
|
1891
|
+
// Only run this module if we have at least one legend - wait for
|
1892
|
+
// it - item. Don't run if the legend is populated by a colorAxis.
|
1893
|
+
// Don't run if legend navigation is disabled.
|
1894
|
+
validate: function() {
|
1895
|
+
return chart.legend && chart.legend.allItems &&
|
1896
|
+
chart.legend.display &&
|
1897
|
+
!(chart.colorAxis && chart.colorAxis.length) &&
|
1898
|
+
(chart.options.legend &&
|
1899
|
+
chart.options.legend.keyboardNavigation &&
|
1900
|
+
chart.options.legend.keyboardNavigation.enabled) !== false;
|
1901
|
+
},
|
1902
|
+
|
1903
|
+
// Make elements focusable and accessible
|
1904
|
+
init: function(direction) {
|
1905
|
+
each(chart.legend.allItems, function(item) {
|
1906
|
+
item.legendGroup.element.setAttribute('tabindex', '-1');
|
1907
|
+
item.legendGroup.element.setAttribute('role', 'button');
|
1908
|
+
item.legendGroup.element.setAttribute(
|
1909
|
+
'aria-label',
|
1910
|
+
stripTags('Toggle visibility of series ' + item.name)
|
1911
|
+
);
|
1912
|
+
});
|
1913
|
+
// Focus first/last item
|
1914
|
+
chart.highlightLegendItem(
|
1915
|
+
direction > 0 ? 0 : chart.legend.allItems.length - 1
|
1916
|
+
);
|
1731
1917
|
}
|
1732
|
-
}
|
1733
|
-
|
1918
|
+
})
|
1919
|
+
];
|
1920
|
+
};
|
1734
1921
|
|
1735
|
-
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
1922
|
+
|
1923
|
+
// Add exit anchor to the chart
|
1924
|
+
// We use this to move focus out of chart whenever we want, by setting focus
|
1925
|
+
// to this div and not preventing the default tab action.
|
1926
|
+
// We also use this when users come back into the chart by tabbing back, in
|
1927
|
+
// order to navigate from the end of the chart.
|
1928
|
+
// Function returns the unbind function for the exit anchor's event handler.
|
1929
|
+
H.Chart.prototype.addExitAnchor = function() {
|
1930
|
+
var chart = this;
|
1931
|
+
chart.tabExitAnchor = doc.createElement('div');
|
1932
|
+
chart.tabExitAnchor.setAttribute('tabindex', '0');
|
1933
|
+
|
1934
|
+
// Hide exit anchor
|
1935
|
+
merge(true, chart.tabExitAnchor.style, {
|
1936
|
+
position: 'absolute',
|
1937
|
+
left: '-9999px',
|
1938
|
+
top: 'auto',
|
1939
|
+
width: '1px',
|
1940
|
+
height: '1px',
|
1941
|
+
overflow: 'hidden'
|
1743
1942
|
});
|
1744
1943
|
|
1745
|
-
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1944
|
+
chart.renderTo.appendChild(chart.tabExitAnchor);
|
1945
|
+
return addEvent(chart.tabExitAnchor, 'focus',
|
1946
|
+
function(ev) {
|
1947
|
+
var e = ev || win.event,
|
1948
|
+
curModule;
|
1749
1949
|
|
1750
|
-
|
1751
|
-
|
1752
|
-
body = table.getElementsByTagName('tbody')[0],
|
1753
|
-
firstRow = head.firstChild.children,
|
1754
|
-
columnHeaderRow = '<tr><td></td>',
|
1755
|
-
cell,
|
1756
|
-
newCell;
|
1950
|
+
// If focusing and we are exiting, do nothing once.
|
1951
|
+
if (!chart.exiting) {
|
1757
1952
|
|
1758
|
-
|
1759
|
-
|
1953
|
+
// Not exiting, means we are coming in backwards
|
1954
|
+
chart.renderTo.focus();
|
1955
|
+
e.preventDefault();
|
1760
1956
|
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
});
|
1957
|
+
// Move to last valid keyboard nav module
|
1958
|
+
// Note the we don't run it, just set the index
|
1959
|
+
chart.keyboardNavigationModuleIndex =
|
1960
|
+
chart.keyboardNavigationModules.length - 1;
|
1961
|
+
curModule = chart.keyboardNavigationModules[
|
1962
|
+
chart.keyboardNavigationModuleIndex
|
1963
|
+
];
|
1769
1964
|
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1965
|
+
// Validate the module
|
1966
|
+
if (curModule.validate && !curModule.validate()) {
|
1967
|
+
// Invalid.
|
1968
|
+
// Move inits next valid module in direction
|
1969
|
+
curModule.move(-1);
|
1970
|
+
} else {
|
1971
|
+
// We have a valid module, init it
|
1972
|
+
curModule.init(-1);
|
1774
1973
|
}
|
1775
|
-
});
|
1776
1974
|
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
columnHeaderRow += '<th scope="col" colspan="' + col.span +
|
1781
|
-
'">' + col.text + '</th>';
|
1782
|
-
});
|
1783
|
-
head.insertAdjacentHTML('afterbegin', columnHeaderRow);
|
1975
|
+
} else {
|
1976
|
+
// Don't skip the next focus, we only skip once.
|
1977
|
+
chart.exiting = false;
|
1784
1978
|
}
|
1785
1979
|
}
|
1786
|
-
|
1980
|
+
);
|
1981
|
+
};
|
1982
|
+
|
1983
|
+
|
1984
|
+
// Add keyboard navigation events on chart load
|
1985
|
+
H.Chart.prototype.callbacks.push(function(chart) {
|
1986
|
+
var a11yOptions = chart.options.accessibility;
|
1987
|
+
if (a11yOptions.enabled && a11yOptions.keyboardNavigation.enabled) {
|
1988
|
+
|
1989
|
+
// Init nav modules. We start at the first module, and as the user
|
1990
|
+
// navigates through the chart the index will increase to use different
|
1991
|
+
// handler modules.
|
1992
|
+
chart.addKeyboardNavigationModules();
|
1993
|
+
chart.keyboardNavigationModuleIndex = 0;
|
1994
|
+
|
1995
|
+
// Make chart container reachable by tab
|
1996
|
+
if (
|
1997
|
+
chart.container.hasAttribute &&
|
1998
|
+
!chart.container.hasAttribute('tabIndex')
|
1999
|
+
) {
|
2000
|
+
chart.container.setAttribute('tabindex', '0');
|
2001
|
+
}
|
2002
|
+
|
2003
|
+
// Add tab exit anchor
|
2004
|
+
if (!chart.tabExitAnchor) {
|
2005
|
+
chart.unbindExitAnchorFocus = chart.addExitAnchor();
|
2006
|
+
}
|
2007
|
+
|
2008
|
+
// Handle keyboard events by routing them to active keyboard nav module
|
2009
|
+
chart.unbindKeydownHandler = addEvent(chart.renderTo, 'keydown',
|
2010
|
+
function(ev) {
|
2011
|
+
var e = ev || win.event,
|
2012
|
+
curNavModule = chart.keyboardNavigationModules[
|
2013
|
+
chart.keyboardNavigationModuleIndex
|
2014
|
+
];
|
2015
|
+
// If there is a nav module for the current index, run it.
|
2016
|
+
// Otherwise, we are outside of the chart in some direction.
|
2017
|
+
if (curNavModule) {
|
2018
|
+
if (curNavModule.run(e)) {
|
2019
|
+
// Successfully handled this key event, stop default
|
2020
|
+
e.preventDefault();
|
2021
|
+
}
|
2022
|
+
}
|
2023
|
+
});
|
2024
|
+
|
2025
|
+
// Add cleanup handlers
|
2026
|
+
addEvent(chart, 'destroy', function() {
|
2027
|
+
if (chart.unbindExitAnchorFocus && chart.tabExitAnchor) {
|
2028
|
+
chart.unbindExitAnchorFocus();
|
2029
|
+
}
|
2030
|
+
if (chart.unbindKeydownHandler && chart.renderTo) {
|
2031
|
+
chart.unbindKeydownHandler();
|
2032
|
+
}
|
2033
|
+
});
|
2034
|
+
}
|
1787
2035
|
});
|
1788
2036
|
|
1789
2037
|
}(Highcharts));
|