1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046 |
- /**
- * @license Highcharts JS v7.0.2 (2019-01-17)
- * Accessibility module
- *
- * (c) 2010-2019 Highsoft AS
- * Author: Oystein Moseng
- *
- * License: www.highcharts.com/license
- */
- 'use strict';
- (function (factory) {
- if (typeof module === 'object' && module.exports) {
- factory['default'] = factory;
- module.exports = factory;
- } else if (typeof define === 'function' && define.amd) {
- define(function () {
- return factory;
- });
- } else {
- factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
- }
- }(function (Highcharts) {
- (function (H) {
- /**
- * Accessibility module - internationalization support
- *
- * (c) 2010-2019 Highsoft AS
- * Author: Øystein Moseng
- *
- * License: www.highcharts.com/license
- */
- var pick = H.pick;
- /**
- * String trim that works for IE6-8 as well.
- *
- * @private
- * @function stringTrim
- *
- * @param {string} str
- * The input string
- *
- * @return {string}
- * The trimmed string
- */
- function stringTrim(str) {
- return str.trim && str.trim() || str.replace(/^\s+|\s+$/g, '');
- }
- /**
- * i18n utility function. Format a single array or plural statement in a format
- * string. If the statement is not an array or plural statement, returns the
- * statement within brackets. Invalid array statements return an empty string.
- *
- * @private
- * @function formatExtendedStatement
- *
- * @param {string} statement
- *
- * @param {Highcharts.Dictionary<*>} ctx
- * Context to apply to the format string.
- *
- * @return {string}
- */
- function formatExtendedStatement(statement, ctx) {
- var eachStart = statement.indexOf('#each('),
- pluralStart = statement.indexOf('#plural('),
- indexStart = statement.indexOf('['),
- indexEnd = statement.indexOf(']'),
- arr,
- result;
- // Dealing with an each-function?
- if (eachStart > -1) {
- var eachEnd = statement.slice(eachStart).indexOf(')') + eachStart,
- preEach = statement.substring(0, eachStart),
- postEach = statement.substring(eachEnd + 1),
- eachStatement = statement.substring(eachStart + 6, eachEnd),
- eachArguments = eachStatement.split(','),
- lenArg = Number(eachArguments[1]),
- len;
- result = '';
- arr = ctx[eachArguments[0]];
- if (arr) {
- lenArg = isNaN(lenArg) ? arr.length : lenArg;
- len = lenArg < 0 ?
- arr.length + lenArg :
- Math.min(lenArg, arr.length); // Overshoot
- // Run through the array for the specified length
- for (var i = 0; i < len; ++i) {
- result += preEach + arr[i] + postEach;
- }
- }
- return result.length ? result : '';
- }
- // Dealing with a plural-function?
- if (pluralStart > -1) {
- var pluralEnd = statement.slice(pluralStart).indexOf(')') + pluralStart,
- pluralStatement = statement.substring(pluralStart + 8, pluralEnd),
- pluralArguments = pluralStatement.split(','),
- num = Number(ctx[pluralArguments[0]]);
- switch (num) {
- case 0:
- result = pick(pluralArguments[4], pluralArguments[1]);
- break;
- case 1:
- result = pick(pluralArguments[2], pluralArguments[1]);
- break;
- case 2:
- result = pick(pluralArguments[3], pluralArguments[1]);
- break;
- default:
- result = pluralArguments[1];
- }
- return result ? stringTrim(result) : '';
- }
- // Array index
- if (indexStart > -1) {
- var arrayName = statement.substring(0, indexStart),
- ix = Number(statement.substring(indexStart + 1, indexEnd)),
- val;
- arr = ctx[arrayName];
- if (!isNaN(ix) && arr) {
- if (ix < 0) {
- val = arr[arr.length + ix];
- // Handle negative overshoot
- if (val === undefined) {
- val = arr[0];
- }
- } else {
- val = arr[ix];
- // Handle positive overshoot
- if (val === undefined) {
- val = arr[arr.length - 1];
- }
- }
- }
- return val !== undefined ? val : '';
- }
- // Standard substitution, delegate to H.format or similar
- return '{' + statement + '}';
- }
- /**
- * i18n formatting function. Extends Highcharts.format() functionality by also
- * handling arrays and plural conditionals. Arrays can be indexed as follows:
- *
- * - Format: 'This is the first index: {myArray[0]}. The last: {myArray[-1]}.'
- *
- * - Context: { myArray: [0, 1, 2, 3, 4, 5] }
- *
- * - Result: 'This is the first index: 0. The last: 5.'
- *
- *
- * They can also be iterated using the #each() function. This will repeat the
- * contents of the bracket expression for each element. Example:
- *
- * - Format: 'List contains: {#each(myArray)cm }'
- *
- * - Context: { myArray: [0, 1, 2] }
- *
- * - Result: 'List contains: 0cm 1cm 2cm '
- *
- *
- * The #each() function optionally takes a length parameter. If positive, this
- * parameter specifies the max number of elements to iterate through. If
- * negative, the function will subtract the number from the length of the array.
- * Use this to stop iterating before the array ends. Example:
- *
- * - Format: 'List contains: {#each(myArray, -1) }and {myArray[-1]}.'
- *
- * - Context: { myArray: [0, 1, 2, 3] }
- *
- * - Result: 'List contains: 0, 1, 2, and 3.'
- *
- *
- * Use the #plural() function to pick a string depending on whether or not a
- * context object is 1. Arguments are #plural(obj, plural, singular). Example:
- *
- * - Format: 'Has {numPoints} {#plural(numPoints, points, point}.'
- *
- * - Context: { numPoints: 5 }
- *
- * - Result: 'Has 5 points.'
- *
- *
- * Optionally there are additional parameters for dual and none: #plural(obj,
- * plural, singular, dual, none). Example:
- *
- * - Format: 'Has {#plural(numPoints, many points, one point, two points,
- * none}.'
- *
- * - Context: { numPoints: 2 }
- *
- * - Result: 'Has two points.'
- *
- *
- * The dual or none parameters will take precedence if they are supplied.
- *
- *
- * @function Highcharts.i18nFormat
- * @requires a11y-i18n
- *
- * @param {string} formatString
- * The string to format.
- *
- * @param {Highcharts.Dictionary<*>} context
- * Context to apply to the format string.
- *
- * @param {Highcharts.Time} time
- * A `Time` instance for date formatting, passed on to H.format().
- *
- * @return {string}
- * The formatted string.
- */
- H.i18nFormat = function (formatString, context, time) {
- var getFirstBracketStatement = function (sourceStr, offset) {
- var str = sourceStr.slice(offset || 0),
- startBracket = str.indexOf('{'),
- endBracket = str.indexOf('}');
- if (startBracket > -1 && endBracket > startBracket) {
- return {
- statement: str.substring(startBracket + 1, endBracket),
- begin: offset + startBracket + 1,
- end: offset + endBracket
- };
- }
- },
- tokens = [],
- bracketRes,
- constRes,
- cursor = 0;
- // Tokenize format string into bracket statements and constants
- do {
- bracketRes = getFirstBracketStatement(formatString, cursor);
- constRes = formatString.substring(
- cursor,
- bracketRes && bracketRes.begin - 1
- );
- // If we have constant content before this bracket statement, add it
- if (constRes.length) {
- tokens.push({
- value: constRes,
- type: 'constant'
- });
- }
- // Add the bracket statement
- if (bracketRes) {
- tokens.push({
- value: bracketRes.statement,
- type: 'statement'
- });
- }
- cursor = bracketRes && bracketRes.end + 1;
- } while (bracketRes);
- // Perform the formatting. The formatArrayStatement function returns the
- // statement in brackets if it is not an array statement, which means it
- // gets picked up by H.format below.
- tokens.forEach(function (token) {
- if (token.type === 'statement') {
- token.value = formatExtendedStatement(token.value, context);
- }
- });
- // Join string back together and pass to H.format to pick up non-array
- // statements.
- return H.format(tokens.reduce(function (acc, cur) {
- return acc + cur.value;
- }, ''), context, time);
- };
- /**
- * Apply context to a format string from lang options of the chart.
- *
- * @function Highcharts.Chart#langFormat
- * @requires a11y-i18n
- *
- * @param {string} langKey
- * Key (using dot notation) into lang option structure.
- *
- * @param {Highcharts.Dictionary<*>} context
- * Context to apply to the format string.
- *
- * @return {string}
- * The formatted string.
- */
- H.Chart.prototype.langFormat = function (langKey, context, time) {
- var keys = langKey.split('.'),
- formatString = this.options.lang,
- i = 0;
- for (; i < keys.length; ++i) {
- formatString = formatString && formatString[keys[i]];
- }
- return typeof formatString === 'string' && H.i18nFormat(
- formatString, context, time
- );
- };
- H.setOptions({
- lang: {
- /**
- * Configure the accessibility strings in the chart. Requires the
- * [accessibility module](//code.highcharts.com/modules/accessibility.js)
- * to be loaded. For a description of the module and information on its
- * features, see [Highcharts Accessibility](
- * http://www.highcharts.com/docs/chart-concepts/accessibility).
- *
- * For more dynamic control over the accessibility functionality, see
- * [accessibility.pointDescriptionFormatter](
- * accessibility.pointDescriptionFormatter),
- * [accessibility.seriesDescriptionFormatter](
- * accessibility.seriesDescriptionFormatter), and
- * [accessibility.screenReaderSectionFormatter](
- * accessibility.screenReaderSectionFormatter).
- *
- * @since 6.0.6
- * @optionparent lang.accessibility
- */
- accessibility: {
- /* eslint-disable max-len */
- screenReaderRegionLabel: 'Chart screen reader information.',
- navigationHint: 'Use regions/landmarks to skip ahead to chart {#plural(numSeries, and navigate between data series,)}',
- defaultChartTitle: 'Chart',
- longDescriptionHeading: 'Long description.',
- noDescription: 'No description available.',
- structureHeading: 'Structure.',
- viewAsDataTable: 'View as data table.',
- chartHeading: 'Chart graphic.',
- chartContainerLabel: 'Interactive chart. {title}. Use up and down arrows to navigate with most screen readers.',
- rangeSelectorMinInput: 'Select start date.',
- rangeSelectorMaxInput: 'Select end date.',
- tableSummary: 'Table representation of chart.',
- mapZoomIn: 'Zoom chart',
- mapZoomOut: 'Zoom out chart',
- rangeSelectorButton: 'Select range {buttonText}',
- legendItem: 'Toggle visibility of series {itemName}',
- /* eslint-enable max-len */
- /**
- * Title element text for the chart SVG element. Leave this
- * empty to disable adding the title element. Browsers will display
- * this content when hovering over elements in the chart. Assistive
- * technology may use this element to label the chart.
- *
- * @since 6.0.8
- */
- svgContainerTitle: '{chartTitle}',
- /**
- * Descriptions of lesser known series types. The relevant
- * description is added to the screen reader information region
- * when these series types are used.
- *
- * @since 6.0.6
- */
- seriesTypeDescriptions: {
- boxplot: 'Box plot charts are typically used to display ' +
- 'groups of statistical data. Each data point in the ' +
- 'chart can have up to 5 values: minimum, lower quartile, ' +
- 'median, upper quartile, and maximum.',
- arearange: 'Arearange charts are line charts displaying a ' +
- 'range between a lower and higher value for each point.',
- areasplinerange: 'These charts are line charts displaying a ' +
- 'range between a lower and higher value for each point.',
- bubble: 'Bubble charts are scatter charts where each data ' +
- 'point also has a size value.',
- columnrange: 'Columnrange charts are column charts ' +
- 'displaying a range between a lower and higher value for ' +
- 'each point.',
- errorbar: 'Errorbar series are used to display the ' +
- 'variability of the data.',
- funnel: 'Funnel charts are used to display reduction of data ' +
- 'in stages.',
- pyramid: 'Pyramid charts consist of a single pyramid with ' +
- 'item heights corresponding to each point value.',
- waterfall: 'A waterfall chart is a column chart where each ' +
- 'column contributes towards a total end value.'
- },
- /**
- * Chart type description strings. This is added to the chart
- * information region.
- *
- * If there is only a single series type used in the chart, we use
- * the format string for the series type, or default if missing.
- * There is one format string for cases where there is only a single
- * series in the chart, and one for multiple series of the same
- * type.
- *
- * @since 6.0.6
- */
- chartTypes: {
- /* eslint-disable max-len */
- emptyChart: 'Empty chart',
- mapTypeDescription: 'Map of {mapTitle} with {numSeries} data series.',
- unknownMap: 'Map of unspecified region with {numSeries} data series.',
- combinationChart: 'Combination chart with {numSeries} data series.',
- defaultSingle: 'Chart with {numPoints} data {#plural(numPoints, points, point)}.',
- defaultMultiple: 'Chart with {numSeries} data series.',
- splineSingle: 'Line chart with {numPoints} data {#plural(numPoints, points, point)}.',
- splineMultiple: 'Line chart with {numSeries} lines.',
- lineSingle: 'Line chart with {numPoints} data {#plural(numPoints, points, point)}.',
- lineMultiple: 'Line chart with {numSeries} lines.',
- columnSingle: 'Bar chart with {numPoints} {#plural(numPoints, bars, bar)}.',
- columnMultiple: 'Bar chart with {numSeries} data series.',
- barSingle: 'Bar chart with {numPoints} {#plural(numPoints, bars, bar)}.',
- barMultiple: 'Bar chart with {numSeries} data series.',
- pieSingle: 'Pie chart with {numPoints} {#plural(numPoints, slices, slice)}.',
- pieMultiple: 'Pie chart with {numSeries} pies.',
- scatterSingle: 'Scatter chart with {numPoints} {#plural(numPoints, points, point)}.',
- scatterMultiple: 'Scatter chart with {numSeries} data series.',
- boxplotSingle: 'Boxplot with {numPoints} {#plural(numPoints, boxes, box)}.',
- boxplotMultiple: 'Boxplot with {numSeries} data series.',
- bubbleSingle: 'Bubble chart with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
- bubbleMultiple: 'Bubble chart with {numSeries} data series.'
- }, /* eslint-enable max-len */
- /**
- * Axis description format strings.
- *
- * @since 6.0.6
- */
- axis: {
- /* eslint-disable max-len */
- xAxisDescriptionSingular: 'The chart has 1 X axis displaying {names[0]}.',
- xAxisDescriptionPlural: 'The chart has {numAxes} X axes displaying {#names.forEach(-1) }and {names[-1]}',
- yAxisDescriptionSingular: 'The chart has 1 Y axis displaying {names[0]}.',
- yAxisDescriptionPlural: 'The chart has {numAxes} Y axes displaying {#names.forEach(-1) }and {names[-1]}'
- }, /* eslint-enable max-len */
- /**
- * Exporting menu format strings for accessibility module.
- *
- * @since 6.0.6
- */
- exporting: {
- chartMenuLabel: 'Chart export',
- menuButtonLabel: 'View export menu',
- exportRegionLabel: 'Chart export menu'
- },
- /**
- * Lang configuration for different series types. For more dynamic
- * control over the series element descriptions, see
- * [accessibility.seriesDescriptionFormatter](
- * accessibility.seriesDescriptionFormatter).
- *
- * @since 6.0.6
- */
- series: {
- /**
- * Lang configuration for the series main summary. Each series
- * type has two modes:
- *
- * 1. This series type is the only series type used in the
- * chart
- *
- * 2. This is a combination chart with multiple series types
- *
- * If a definition does not exist for the specific series type
- * and mode, the 'default' lang definitions are used.
- *
- * @since 6.0.6
- */
- summary: {
- /* eslint-disable max-len */
- 'default': '{name}, series {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
- defaultCombination: '{name}, series {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
- line: '{name}, line {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
- lineCombination: '{name}, series {ix} of {numSeries}. Line with {numPoints} data {#plural(numPoints, points, point)}.',
- spline: '{name}, line {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
- splineCombination: '{name}, series {ix} of {numSeries}. Line with {numPoints} data {#plural(numPoints, points, point)}.',
- column: '{name}, bar series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bars, bar)}.',
- columnCombination: '{name}, series {ix} of {numSeries}. Bar series with {numPoints} {#plural(numPoints, bars, bar)}.',
- bar: '{name}, bar series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bars, bar)}.',
- barCombination: '{name}, series {ix} of {numSeries}. Bar series with {numPoints} {#plural(numPoints, bars, bar)}.',
- pie: '{name}, pie {ix} of {numSeries} with {numPoints} {#plural(numPoints, slices, slice)}.',
- pieCombination: '{name}, series {ix} of {numSeries}. Pie with {numPoints} {#plural(numPoints, slices, slice)}.',
- scatter: '{name}, scatter plot {ix} of {numSeries} with {numPoints} {#plural(numPoints, points, point)}.',
- scatterCombination: '{name}, series {ix} of {numSeries}, scatter plot with {numPoints} {#plural(numPoints, points, point)}.',
- boxplot: '{name}, boxplot {ix} of {numSeries} with {numPoints} {#plural(numPoints, boxes, box)}.',
- boxplotCombination: '{name}, series {ix} of {numSeries}. Boxplot with {numPoints} {#plural(numPoints, boxes, box)}.',
- bubble: '{name}, bubble series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
- bubbleCombination: '{name}, series {ix} of {numSeries}. Bubble series with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
- map: '{name}, map {ix} of {numSeries} with {numPoints} {#plural(numPoints, areas, area)}.',
- mapCombination: '{name}, series {ix} of {numSeries}. Map with {numPoints} {#plural(numPoints, areas, area)}.',
- mapline: '{name}, line {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
- maplineCombination: '{name}, series {ix} of {numSeries}. Line with {numPoints} data {#plural(numPoints, points, point)}.',
- mapbubble: '{name}, bubble series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
- mapbubbleCombination: '{name}, series {ix} of {numSeries}. Bubble series with {numPoints} {#plural(numPoints, bubbles, bubble)}.'
- }, /* eslint-enable max-len */
- /**
- * User supplied description text. This is added after the main
- * summary if present.
- *
- * @since 6.0.6
- */
- description: '{description}',
- /**
- * xAxis description for series if there are multiple xAxes in
- * the chart.
- *
- * @since 6.0.6
- */
- xAxisDescription: 'X axis, {name}',
- /**
- * yAxis description for series if there are multiple yAxes in
- * the chart.
- *
- * @since 6.0.6
- */
- yAxisDescription: 'Y axis, {name}'
- }
- }
- }
- });
- }(Highcharts));
- (function (H) {
- /**
- * Accessibility module - Screen Reader support
- *
- * (c) 2010-2017 Highsoft AS
- * Author: Oystein Moseng
- *
- * License: www.highcharts.com/license
- */
- var win = H.win,
- doc = win.document,
- erase = H.erase,
- addEvent = H.addEvent,
- merge = H.merge,
- // CSS style to hide element from visual users while still exposing it to
- // screen readers
- hiddenStyle = {
- position: 'absolute',
- left: '-9999px',
- top: 'auto',
- width: '1px',
- height: '1px',
- overflow: 'hidden'
- };
- // If a point has one of the special keys defined, we expose all keys to the
- // screen reader.
- H.Series.prototype.commonKeys = ['name', 'id', 'category', 'x', 'value', 'y'];
- H.Series.prototype.specialKeys = [
- 'z', 'open', 'high', 'q3', 'median', 'q1', 'low', 'close'
- ];
- if (H.seriesTypes.pie) {
- // A pie is always simple. Don't quote me on that.
- H.seriesTypes.pie.prototype.specialKeys = [];
- }
- /**
- * HTML encode some characters vulnerable for XSS.
- *
- * @private
- * @function htmlencode
- *
- * @param {string} html
- * The input string.
- *
- * @return {string}
- * The excaped string.
- */
- function htmlencode(html) {
- return html
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''')
- .replace(/\//g, '/');
- }
- /**
- * Strip HTML tags away from a string. Used for aria-label attributes, painting
- * on a canvas will fail if the text contains tags.
- *
- * @private
- * @function stripTags
- *
- * @param {string} s
- * The input string.
- *
- * @return {string}
- * The filtered string.
- */
- function stripTags(s) {
- return typeof s === 'string' ? s.replace(/<\/?[^>]+(>|$)/g, '') : s;
- }
- // Accessibility options
- H.setOptions({
- /**
- * Options for configuring accessibility for the chart. Requires the
- * [accessibility module](https://code.highcharts.com/modules/accessibility.js)
- * to be loaded. For a description of the module and information
- * on its features, see
- * [Highcharts Accessibility](http://www.highcharts.com/docs/chart-concepts/accessibility).
- *
- * @since 5.0.0
- * @optionparent accessibility
- */
- accessibility: {
- /**
- * Whether or not to add series descriptions to charts with a single
- * series.
- *
- * @type {boolean}
- * @default false
- * @since 5.0.0
- * @apioption accessibility.describeSingleSeries
- */
- /**
- * Function to run upon clicking the "View as Data Table" link in the
- * screen reader region.
- *
- * By default Highcharts will insert and set focus to a data table
- * representation of the chart.
- *
- * @type {Function}
- * @since 5.0.0
- * @apioption accessibility.onTableAnchorClick
- */
- /**
- * Date format to use for points on datetime axes when describing them
- * to screen reader users.
- *
- * Defaults to the same format as in tooltip.
- *
- * For an overview of the replacement codes, see
- * [dateFormat](/class-reference/Highcharts#dateFormat).
- *
- * @see [pointDateFormatter](#accessibility.pointDateFormatter)
- *
- * @type {string}
- * @since 5.0.0
- * @apioption accessibility.pointDateFormat
- */
- /**
- * Formatter function to determine the date/time format used with
- * points on datetime axes when describing them to screen reader users.
- * Receives one argument, `point`, referring to the point to describe.
- * Should return a date format string compatible with
- * [dateFormat](/class-reference/Highcharts#dateFormat).
- *
- * @see [pointDateFormat](#accessibility.pointDateFormat)
- *
- * @type {Function}
- * @since 5.0.0
- * @apioption accessibility.pointDateFormatter
- */
- /**
- * Formatter function to use instead of the default for point
- * descriptions.
- * Receives one argument, `point`, referring to the point to describe.
- * Should return a String with the description of the point for a screen
- * reader user.
- *
- * @see [point.description](#series.line.data.description)
- *
- * @type {Function}
- * @since 5.0.0
- * @apioption accessibility.pointDescriptionFormatter
- */
- /**
- * Formatter function to use instead of the default for series
- * descriptions. Receives one argument, `series`, referring to the
- * series to describe. Should return a String with the description of
- * the series for a screen reader user.
- *
- * @see [series.description](#plotOptions.series.description)
- *
- * @type {Function}
- * @since 5.0.0
- * @apioption accessibility.seriesDescriptionFormatter
- */
- /**
- * Enable accessibility features for the chart.
- *
- * @since 5.0.0
- */
- enabled: true,
- /**
- * When a series contains more points than this, we no longer expose
- * information about individual points to screen readers.
- *
- * Set to `false` to disable.
- *
- * @type {false|number}
- * @since 5.0.0
- */
- pointDescriptionThreshold: false, // set to false to disable
- /**
- * A formatter function to create the HTML contents of the hidden screen
- * reader information region. Receives one argument, `chart`, referring
- * to the chart object. Should return a String with the HTML content
- * of the region.
- *
- * The link to view the chart as a data table will be added
- * automatically after the custom HTML content.
- *
- * @type {Function}
- * @default undefined
- * @since 5.0.0
- */
- screenReaderSectionFormatter: function (chart) {
- var options = chart.options,
- chartTypes = chart.types || [],
- formatContext = {
- chart: chart,
- numSeries: chart.series && chart.series.length
- },
- // Build axis info - but not for pies and maps. Consider not
- // adding for certain other types as well (funnel, pyramid?)
- axesDesc = (
- chartTypes.length === 1 && chartTypes[0] === 'pie' ||
- chartTypes[0] === 'map'
- ) && {} || chart.getAxesDescription();
- return '<div>' + chart.langFormat(
- 'accessibility.navigationHint', formatContext
- ) + '</div><h3>' +
- (
- options.title.text ?
- htmlencode(options.title.text) :
- chart.langFormat(
- 'accessibility.defaultChartTitle', formatContext
- )
- ) +
- (
- options.subtitle && options.subtitle.text ?
- '. ' + htmlencode(options.subtitle.text) :
- ''
- ) +
- '</h3><h4>' + chart.langFormat(
- 'accessibility.longDescriptionHeading', formatContext
- ) + '</h4><div>' +
- (
- options.chart.description || chart.langFormat(
- 'accessibility.noDescription', formatContext
- )
- ) +
- '</div><h4>' + chart.langFormat(
- 'accessibility.structureHeading', formatContext
- ) + '</h4><div>' +
- (
- options.chart.typeDescription ||
- chart.getTypeDescription()
- ) + '</div>' +
- (axesDesc.xAxis ? (
- '<div>' + axesDesc.xAxis + '</div>'
- ) : '') +
- (axesDesc.yAxis ? (
- '<div>' + axesDesc.yAxis + '</div>'
- ) : '');
- }
- }
- });
- /**
- * A text description of the chart.
- *
- * If the Accessibility module is loaded, this is included by default
- * as a long description of the chart and its contents in the hidden
- * screen reader information region.
- *
- * @see [typeDescription](#chart.typeDescription)
- *
- * @type {string}
- * @since 5.0.0
- * @apioption chart.description
- */
- /**
- * A text description of the chart type.
- *
- * If the Accessibility module is loaded, this will be included in the
- * description of the chart in the screen reader information region.
- *
- *
- * Highcharts will by default attempt to guess the chart type, but for
- * more complex charts it is recommended to specify this property for
- * clarity.
- *
- * @type {string}
- * @since 5.0.0
- * @apioption chart.typeDescription
- */
- /**
- * Utility function. Reverses child nodes of a DOM element.
- *
- * @private
- * @function reverseChildNodes
- *
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} node
- */
- function reverseChildNodes(node) {
- var i = node.childNodes.length;
- while (i--) {
- node.appendChild(node.childNodes[i]);
- }
- }
- // Whenever drawing series, put info on DOM elements
- H.addEvent(H.Series, 'afterRender', function () {
- if (this.chart.options.accessibility.enabled) {
- this.setA11yDescription();
- }
- });
- /**
- * Put accessible info on series and points of a series.
- *
- * @private
- * @function Highcharts.Series#setA11yDescription
- */
- H.Series.prototype.setA11yDescription = function () {
- var a11yOptions = this.chart.options.accessibility,
- firstPointEl = (
- this.points &&
- this.points.length &&
- this.points[0].graphic &&
- this.points[0].graphic.element
- ),
- seriesEl = (
- firstPointEl &&
- firstPointEl.parentNode || this.graph &&
- this.graph.element || this.group &&
- this.group.element
- ); // Could be tracker series depending on series type
- if (seriesEl) {
- // For some series types the order of elements do not match the order of
- // points in series. In that case we have to reverse them in order for
- // AT to read them out in an understandable order
- if (seriesEl.lastChild === firstPointEl) {
- reverseChildNodes(seriesEl);
- }
- // Make individual point elements accessible if possible. Note: If
- // markers are disabled there might not be any elements there to make
- // accessible.
- if (
- this.points && (
- this.points.length < a11yOptions.pointDescriptionThreshold ||
- a11yOptions.pointDescriptionThreshold === false
- )
- ) {
- this.points.forEach(function (point) {
- if (point.graphic) {
- point.graphic.element.setAttribute('role', 'img');
- point.graphic.element.setAttribute('tabindex', '-1');
- point.graphic.element.setAttribute('aria-label', stripTags(
- point.series.options.pointDescriptionFormatter &&
- point.series.options.pointDescriptionFormatter(point) ||
- a11yOptions.pointDescriptionFormatter &&
- a11yOptions.pointDescriptionFormatter(point) ||
- point.buildPointInfoString()
- ));
- }
- });
- }
- // Make series element accessible
- if (this.chart.series.length > 1 || a11yOptions.describeSingleSeries) {
- seriesEl.setAttribute(
- 'role',
- this.options.exposeElementToA11y ? 'img' : 'region'
- );
- seriesEl.setAttribute('tabindex', '-1');
- seriesEl.setAttribute(
- 'aria-label',
- stripTags(
- a11yOptions.seriesDescriptionFormatter &&
- a11yOptions.seriesDescriptionFormatter(this) ||
- this.buildSeriesInfoString()
- )
- );
- }
- }
- };
- /**
- * Return string with information about series.
- *
- * @private
- * @function Highcharts.Series#buildSeriesInfoString
- *
- * @return {string}
- */
- H.Series.prototype.buildSeriesInfoString = function () {
- var chart = this.chart,
- desc = this.description || this.options.description,
- description = desc && chart.langFormat(
- 'accessibility.series.description', {
- description: desc,
- series: this
- }
- ),
- xAxisInfo = chart.langFormat(
- 'accessibility.series.xAxisDescription',
- {
- name: this.xAxis && this.xAxis.getDescription(),
- series: this
- }
- ),
- yAxisInfo = chart.langFormat(
- 'accessibility.series.yAxisDescription',
- {
- name: this.yAxis && this.yAxis.getDescription(),
- series: this
- }
- ),
- summaryContext = {
- name: this.name || '',
- ix: this.index + 1,
- numSeries: chart.series.length,
- numPoints: this.points.length,
- series: this
- },
- combination = chart.types.length === 1 ? '' : 'Combination',
- summary = chart.langFormat(
- 'accessibility.series.summary.' + this.type + combination,
- summaryContext
- ) || chart.langFormat(
- 'accessibility.series.summary.default' + combination,
- summaryContext
- );
- return summary + (description ? ' ' + description : '') + (
- chart.yAxis.length > 1 && this.yAxis ?
- ' ' + yAxisInfo : ''
- ) + (
- chart.xAxis.length > 1 && this.xAxis ?
- ' ' + xAxisInfo : ''
- );
- };
- /**
- * Return string with information about point.
- *
- * @private
- * @function Highcharts.Point#buildPointInfoString
- *
- * @return {string}
- */
- H.Point.prototype.buildPointInfoString = function () {
- var point = this,
- series = point.series,
- a11yOptions = series.chart.options.accessibility,
- infoString = '',
- dateTimePoint = series.xAxis && series.xAxis.isDatetimeAxis,
- timeDesc =
- dateTimePoint &&
- series.chart.time.dateFormat(
- a11yOptions.pointDateFormatter &&
- a11yOptions.pointDateFormatter(point) ||
- a11yOptions.pointDateFormat ||
- H.Tooltip.prototype.getXDateFormat.call(
- {
- getDateFormat: H.Tooltip.prototype.getDateFormat,
- chart: series.chart
- },
- point,
- series.chart.options.tooltip,
- series.xAxis
- ),
- point.x
- ),
- hasSpecialKey = H.find(series.specialKeys, function (key) {
- return point[key] !== undefined;
- });
- // If the point has one of the less common properties defined, display all
- // that are defined
- if (hasSpecialKey) {
- if (dateTimePoint) {
- infoString = timeDesc;
- }
- series.commonKeys.concat(series.specialKeys).forEach(function (key) {
- if (point[key] !== undefined && !(dateTimePoint && key === 'x')) {
- infoString += (infoString ? '. ' : '') +
- key + ', ' +
- point[key];
- }
- });
- } else {
- // Pick and choose properties for a succint label
- infoString =
- (
- this.name ||
- timeDesc ||
- this.category ||
- this.id ||
- 'x, ' + this.x
- ) + ', ' +
- (this.value !== undefined ? this.value : this.y);
- }
- return (this.index + 1) + '. ' + infoString + '.' +
- (this.description ? ' ' + this.description : '');
- };
- /**
- * Get descriptive label for axis.
- *
- * @private
- * @function Highcharts.Axis#getDescription
- *
- * @return {string}
- */
- H.Axis.prototype.getDescription = function () {
- return (
- this.userOptions && this.userOptions.description ||
- this.axisTitle && this.axisTitle.textStr ||
- this.options.id ||
- this.categories && 'categories' ||
- this.isDatetimeAxis && 'Time' ||
- 'values'
- );
- };
- // Whenever adding or removing series, keep track of types present in chart
- addEvent(H.Series, 'afterInit', function () {
- var chart = this.chart;
- if (chart.options.accessibility.enabled) {
- chart.types = chart.types || [];
- // Add type to list if does not exist
- if (chart.types.indexOf(this.type) < 0) {
- chart.types.push(this.type);
- }
- }
- });
- addEvent(H.Series, 'remove', function () {
- var chart = this.chart,
- removedSeries = this,
- hasType = false;
- // Check if any of the other series have the same type as this one.
- // Otherwise remove it from the list.
- chart.series.forEach(function (s) {
- if (
- s !== removedSeries &&
- chart.types.indexOf(removedSeries.type) < 0
- ) {
- hasType = true;
- }
- });
- if (!hasType) {
- erase(chart.types, removedSeries.type);
- }
- });
- /**
- * Return simplified description of chart type. Some types will not be familiar
- * to most screen reader users, but in those cases we try to add a description
- * of the type.
- *
- * @private
- * @function Highcharts.Chart#getTypeDescription
- *
- * @return {string}
- */
- H.Chart.prototype.getTypeDescription = function () {
- var firstType = this.types && this.types[0],
- firstSeries = this.series && this.series[0] || {},
- mapTitle = firstSeries.mapTitle,
- typeDesc = this.langFormat(
- 'accessibility.seriesTypeDescriptions.' + firstType,
- { chart: this }
- ),
- formatContext = {
- numSeries: this.series.length,
- numPoints: firstSeries.points && firstSeries.points.length,
- chart: this,
- mapTitle: mapTitle
- },
- multi = this.series && this.series.length === 1 ? 'Single' : 'Multiple';
- if (!firstType) {
- return this.langFormat(
- 'accessibility.chartTypes.emptyChart', formatContext
- );
- }
- if (firstType === 'map') {
- return mapTitle ?
- this.langFormat(
- 'accessibility.chartTypes.mapTypeDescription',
- formatContext
- ) :
- this.langFormat(
- 'accessibility.chartTypes.unknownMap',
- formatContext
- );
- }
- if (this.types.length > 1) {
- return this.langFormat(
- 'accessibility.chartTypes.combinationChart', formatContext
- );
- }
- return (
- this.langFormat(
- 'accessibility.chartTypes.' + firstType + multi,
- formatContext
- ) ||
- this.langFormat(
- 'accessibility.chartTypes.default' + multi,
- formatContext
- )
- ) +
- (typeDesc ? ' ' + typeDesc : '');
- };
- /**
- * Return object with text description of each of the chart's axes.
- *
- * @private
- * @function Highcharts.Chart#getAxesDescription
- *
- * @return {*}
- */
- H.Chart.prototype.getAxesDescription = function () {
- var numXAxes = this.xAxis.length,
- numYAxes = this.yAxis.length,
- desc = {};
- if (numXAxes) {
- desc.xAxis = this.langFormat(
- 'accessibility.axis.xAxisDescription' + (
- numXAxes > 1 ? 'Plural' : 'Singular'
- ),
- {
- chart: this,
- names: this.xAxis.map(function (axis) {
- return axis.getDescription();
- }),
- numAxes: numXAxes
- }
- );
- }
- if (numYAxes) {
- desc.yAxis = this.langFormat(
- 'accessibility.axis.yAxisDescription' + (
- numYAxes > 1 ? 'Plural' : 'Singular'
- ),
- {
- chart: this,
- names: this.yAxis.map(function (axis) {
- return axis.getDescription();
- }),
- numAxes: numYAxes
- }
- );
- }
- return desc;
- };
- /**
- * Set a11y attribs on exporting menu.
- *
- * @private
- * @function Highcharts.Chart#addAccessibleContextMenuAttribs
- */
- H.Chart.prototype.addAccessibleContextMenuAttribs = function () {
- var exportList = this.exportDivElements;
- if (exportList) {
- // Set tabindex on the menu items to allow focusing by script
- // Set role to give screen readers a chance to pick up the contents
- exportList.forEach(function (item) {
- if (item.tagName === 'DIV' &&
- !(item.children && item.children.length)) {
- item.setAttribute('role', 'menuitem');
- item.setAttribute('tabindex', -1);
- }
- });
- // Set accessibility properties on parent div
- exportList[0].parentNode.setAttribute('role', 'menu');
- exportList[0].parentNode.setAttribute(
- 'aria-label',
- this.langFormat(
- 'accessibility.exporting.chartMenuLabel', { chart: this }
- )
- );
- }
- };
- /**
- * Add screen reader region to chart. tableId is the HTML id of the table to
- * focus when clicking the table anchor in the screen reader region.
- *
- * @private
- * @function Highcharts.Chart#addScreenReaderRegion
- *
- * @param {string} id
- *
- * @param {string} tableId
- */
- H.Chart.prototype.addScreenReaderRegion = function (id, tableId) {
- var chart = this,
- hiddenSection = chart.screenReaderRegion = doc.createElement('div'),
- tableShortcut = doc.createElement('h4'),
- tableShortcutAnchor = doc.createElement('a'),
- chartHeading = doc.createElement('h4');
- hiddenSection.setAttribute('id', id);
- hiddenSection.setAttribute('role', 'region');
- hiddenSection.setAttribute(
- 'aria-label',
- chart.langFormat(
- 'accessibility.screenReaderRegionLabel', { chart: this }
- )
- );
- hiddenSection.innerHTML = chart.options.accessibility
- .screenReaderSectionFormatter(chart);
- // Add shortcut to data table if export-data is loaded
- if (chart.getCSV) {
- tableShortcutAnchor.innerHTML = chart.langFormat(
- 'accessibility.viewAsDataTable', { chart: chart }
- );
- tableShortcutAnchor.href = '#' + tableId;
- // Make this unreachable by user tabbing
- tableShortcutAnchor.setAttribute('tabindex', '-1');
- tableShortcutAnchor.onclick =
- chart.options.accessibility.onTableAnchorClick || function () {
- chart.viewData();
- doc.getElementById(tableId).focus();
- };
- tableShortcut.appendChild(tableShortcutAnchor);
- hiddenSection.appendChild(tableShortcut);
- }
- // Note: JAWS seems to refuse to read aria-label on the container, so add an
- // h4 element as title for the chart.
- chartHeading.innerHTML = chart.langFormat(
- 'accessibility.chartHeading', { chart: chart }
- );
- chart.renderTo.insertBefore(chartHeading, chart.renderTo.firstChild);
- chart.renderTo.insertBefore(hiddenSection, chart.renderTo.firstChild);
- // Hide the section and the chart heading
- merge(true, chartHeading.style, hiddenStyle);
- merge(true, hiddenSection.style, hiddenStyle);
- };
- // Make chart container accessible, and wrap table functionality.
- H.Chart.prototype.callbacks.push(function (chart) {
- var options = chart.options,
- a11yOptions = options.accessibility;
- if (!a11yOptions.enabled) {
- return;
- }
- var titleElement,
- descElement = chart.container.getElementsByTagName('desc')[0],
- textElements = chart.container.getElementsByTagName('text'),
- titleId = 'highcharts-title-' + chart.index,
- tableId = 'highcharts-data-table-' + chart.index,
- hiddenSectionId = 'highcharts-information-region-' + chart.index,
- chartTitle = options.title.text || chart.langFormat(
- 'accessibility.defaultChartTitle', { chart: chart }
- ),
- svgContainerTitle = stripTags(chart.langFormat(
- 'accessibility.svgContainerTitle', {
- chartTitle: chartTitle
- }
- ));
- // Add SVG title tag if it is set
- if (svgContainerTitle.length) {
- titleElement = doc.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'title'
- );
- titleElement.textContent = svgContainerTitle;
- titleElement.id = titleId;
- descElement.parentNode.insertBefore(titleElement, descElement);
- }
- chart.renderTo.setAttribute('role', 'region');
- chart.renderTo.setAttribute(
- 'aria-label',
- chart.langFormat(
- 'accessibility.chartContainerLabel',
- {
- title: stripTags(chartTitle),
- chart: chart
- }
- )
- );
- // Set screen reader properties on export menu
- if (
- chart.exportSVGElements &&
- chart.exportSVGElements[0] &&
- chart.exportSVGElements[0].element
- ) {
- // Set event handler on button
- var button = chart.exportSVGElements[0].element,
- oldExportCallback = button.onclick;
- button.onclick = function () {
- oldExportCallback.apply(
- this,
- Array.prototype.slice.call(arguments)
- );
- chart.addAccessibleContextMenuAttribs();
- chart.highlightExportItem(0);
- };
- // Set props on button
- button.setAttribute('role', 'button');
- button.setAttribute(
- 'aria-label',
- chart.langFormat(
- 'accessibility.exporting.menuButtonLabel', { chart: chart }
- )
- );
- // Set props on group
- chart.exportingGroup.element.setAttribute('role', 'region');
- chart.exportingGroup.element.setAttribute(
- 'aria-label',
- chart.langFormat(
- 'accessibility.exporting.exportRegionLabel', { chart: chart }
- )
- );
- }
- // 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
- // by default part of the page's tabindex unless we set them to -1.
- if (chart.rangeSelector) {
- ['minInput', 'maxInput'].forEach(function (key, i) {
- if (chart.rangeSelector[key]) {
- chart.rangeSelector[key].setAttribute('tabindex', '-1');
- chart.rangeSelector[key].setAttribute('role', 'textbox');
- chart.rangeSelector[key].setAttribute(
- 'aria-label',
- chart.langFormat(
- 'accessibility.rangeSelector' +
- (i ? 'MaxInput' : 'MinInput'), { chart: chart }
- )
- );
- }
- });
- }
- // Hide text elements from screen readers
- [].forEach.call(textElements, function (el) {
- el.setAttribute('aria-hidden', 'true');
- });
- // Add top-secret screen reader region
- chart.addScreenReaderRegion(hiddenSectionId, tableId);
- // Add ID and summary attr to table HTML
- addEvent(chart, 'afterGetTable', function (e) {
- e.html = e.html
- .replace(
- '<table ',
- '<table summary="' + chart.langFormat(
- 'accessibility.tableSummary', { chart: chart }
- ) + '"'
- );
- });
- });
- }(Highcharts));
- (function (H) {
- /**
- * Accessibility module - Keyboard navigation
- *
- * (c) 2010-2017 Highsoft AS
- * Author: Oystein Moseng
- *
- * License: www.highcharts.com/license
- */
- var win = H.win,
- doc = win.document,
- addEvent = H.addEvent,
- fireEvent = H.fireEvent,
- merge = H.merge,
- pick = H.pick;
- /*
- * Add focus border functionality to SVGElements. Draws a new rect on top of
- * element around its bounding box.
- */
- H.extend(H.SVGElement.prototype, {
- /**
- * @private
- * @function Highcharts.SVGElement#addFocusBorder
- *
- * @param {number} margin
- *
- * @param {Higcharts.CSSObject} style
- */
- addFocusBorder: function (margin, style) {
- // Allow updating by just adding new border
- if (this.focusBorder) {
- this.removeFocusBorder();
- }
- // Add the border rect
- var bb = this.getBBox(),
- pad = pick(margin, 3);
- this.focusBorder = this.renderer.rect(
- bb.x - pad,
- bb.y - pad,
- bb.width + 2 * pad,
- bb.height + 2 * pad,
- style && style.borderRadius
- )
- .addClass('highcharts-focus-border')
- .attr({
- zIndex: 99
- })
- .add(this.parentGroup);
- if (!this.renderer.styledMode) {
- this.focusBorder.attr({
- stroke: style && style.stroke,
- 'stroke-width': style && style.strokeWidth
- });
- }
- },
- /**
- * @private
- * @function Highcharts.SVGElement#removeFocusBorder
- */
- removeFocusBorder: function () {
- if (this.focusBorder) {
- this.focusBorder.destroy();
- delete this.focusBorder;
- }
- }
- });
- /*
- * Set for which series types it makes sense to move to the closest point with
- * up/down arrows, and which series types should just move to next series.
- */
- H.Series.prototype.keyboardMoveVertical = true;
- ['column', 'pie'].forEach(function (type) {
- if (H.seriesTypes[type]) {
- H.seriesTypes[type].prototype.keyboardMoveVertical = false;
- }
- });
- /**
- * Strip HTML tags away from a string. Used for aria-label attributes, painting
- * on a canvas will fail if the text contains tags.
- *
- * @private
- * @function stripTags
- *
- * @param {string} s
- * The input string
- *
- * @return {string}
- * The filtered string
- */
- function stripTags(s) {
- return typeof s === 'string' ? s.replace(/<\/?[^>]+(>|$)/g, '') : s;
- }
- /**
- * Get the index of a point in a series. This is needed when using e.g. data
- * grouping.
- *
- * @private
- * @function getPointIndex
- *
- * @param {Highcharts.Point} point
- * The point to find index of.
- *
- * @return {number}
- * The index in the series.points array of the point.
- */
- function getPointIndex(point) {
- var index = point.index,
- points = point.series.points,
- i = points.length;
- if (points[index] !== point) {
- while (i--) {
- if (points[i] === point) {
- return i;
- }
- }
- } else {
- return index;
- }
- }
- // Set default keyboard navigation options
- H.setOptions({
- /**
- * @since 5.0.0
- * @optionparent accessibility
- */
- accessibility: {
- /**
- * Options for keyboard navigation.
- *
- * @since 5.0.0
- */
- keyboardNavigation: {
- /**
- * Enable keyboard navigation for the chart.
- *
- * @since 5.0.0
- */
- enabled: true,
- /**
- * Options for the focus border drawn around elements while
- * navigating through them.
- *
- * @sample highcharts/accessibility/custom-focus
- * Custom focus ring
- *
- * @since 6.0.3
- */
- focusBorder: {
- /**
- * Enable/disable focus border for chart.
- *
- * @since 6.0.3
- */
- enabled: true,
- /**
- * Hide the browser's default focus indicator.
- *
- * @since 6.0.4
- */
- hideBrowserFocusOutline: true,
- /**
- * Style options for the focus border drawn around elements
- * while navigating through them. Note that some browsers in
- * addition draw their own borders for focused elements. These
- * automatic borders can not be styled by Highcharts.
- *
- * In styled mode, the border is given the
- * `.highcharts-focus-border` class.
- *
- * @type {Highcharts.CSSObject}
- * @default {"color": "#335cad", "lineWidth": 2, "borderRadius": 3}
- * @since 6.0.3
- */
- style: {
- /** @ignore-option */
- color: '#335cad',
- /** @ignore-option */
- lineWidth: 2,
- /** @ignore-option */
- borderRadius: 3
- },
- /**
- * Focus border margin around the elements.
- *
- * @since 6.0.3
- */
- margin: 2
- },
- /**
- * Set the keyboard navigation mode for the chart. Can be "normal"
- * or "serialize". In normal mode, left/right arrow keys move
- * between points in a series, while up/down arrow keys move between
- * series. Up/down navigation acts intelligently to figure out which
- * series makes sense to move to from any given point.
- *
- * In "serialize" mode, points are instead navigated as a single
- * list. Left/right behaves as in "normal" mode. Up/down arrow keys
- * will behave like left/right. This is useful for unifying
- * navigation behavior with/without screen readers enabled.
- *
- * @type {string}
- * @default normal
- * @since 6.0.4
- * @validvalue ["normal", "serialize"]
- * @apioption accessibility.keyboardNavigation.mode
- */
- /**
- * Skip null points when navigating through points with the
- * keyboard.
- *
- * @since 5.0.0
- */
- skipNullPoints: true
- }
- }
- });
- /**
- * Keyboard navigation for the legend. Requires the Accessibility module.
- *
- * @since 5.0.14
- * @apioption legend.keyboardNavigation
- */
- /**
- * Enable/disable keyboard navigation for the legend. Requires the Accessibility
- * module.
- *
- * @see [accessibility.keyboardNavigation](
- * #accessibility.keyboardNavigation.enabled)
- *
- * @type {boolean}
- * @default true
- * @since 5.0.13
- * @apioption legend.keyboardNavigation.enabled
- */
- /**
- * Abstraction layer for keyboard navigation. Keep a map of keyCodes to handler
- * functions, and a next/prev move handler for tab order. The module's keyCode
- * handlers determine when to move to another module. Validate holds a function
- * to determine if there are prerequisites for this module to run that are not
- * met. Init holds a function to run once before any keyCodes are interpreted.
- * Terminate holds a function to run once before moving to next/prev module.
- *
- * @private
- * @class
- * @name KeyboardNavigationModule
- *
- * @param {Highcharts.Chart} chart
- * The chart object keeps track of a list of KeyboardNavigationModules.
- *
- * @param {*} options
- */
- function KeyboardNavigationModule(chart, options) {
- this.chart = chart;
- this.id = options.id;
- this.keyCodeMap = options.keyCodeMap;
- this.validate = options.validate;
- this.init = options.init;
- this.terminate = options.terminate;
- }
- KeyboardNavigationModule.prototype = {
- /**
- * Find handler function(s) for key code in the keyCodeMap and run it.
- *
- * @private
- * @function KeyboardNavigationModule#run
- *
- * @param {global.Event} e
- *
- * @return {boolean}
- */
- run: function (e) {
- var navModule = this,
- keyCode = e.which || e.keyCode,
- found = false,
- handled = false;
- this.keyCodeMap.forEach(function (codeSet) {
- if (codeSet[0].indexOf(keyCode) > -1) {
- found = true;
- handled = codeSet[1].call(navModule, keyCode, e) !== false;
- }
- });
- // Default tab handler, move to next/prev module
- if (!found && keyCode === 9) {
- handled = this.move(e.shiftKey ? -1 : 1);
- }
- return handled;
- },
- /**
- * Move to next/prev valid module, or undefined if none, and init it.
- * Returns true on success and false if there is no valid module to move to.
- *
- * @private
- * @function KeyboardNavigationModule#move
- *
- * @param {number} direction
- *
- * @return {boolean}
- */
- move: function (direction) {
- var chart = this.chart;
- if (this.terminate) {
- this.terminate(direction);
- }
- chart.keyboardNavigationModuleIndex += direction;
- var newModule = chart.keyboardNavigationModules[
- chart.keyboardNavigationModuleIndex
- ];
- // Remove existing focus border if any
- if (chart.focusElement) {
- chart.focusElement.removeFocusBorder();
- }
- // Verify new module
- if (newModule) {
- if (newModule.validate && !newModule.validate()) {
- return this.move(direction); // Invalid module, recurse
- }
- if (newModule.init) {
- newModule.init(direction); // Valid module, init it
- return true;
- }
- }
- // No module
- chart.keyboardNavigationModuleIndex = 0; // Reset counter
- // Set focus to chart or exit anchor depending on direction
- if (direction > 0) {
- this.chart.exiting = true;
- this.chart.tabExitAnchor.focus();
- } else {
- this.chart.renderTo.focus();
- }
- return false;
- }
- };
- /**
- * Utility function to attempt to fake a click event on an element.
- *
- * @private
- * @function fakeClickEvent
- *
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement}
- */
- function fakeClickEvent(element) {
- var fakeEvent;
- if (element && element.onclick && doc.createEvent) {
- fakeEvent = doc.createEvent('Events');
- fakeEvent.initEvent('click', true, false);
- element.onclick(fakeEvent);
- }
- }
- /**
- * Determine if a series should be skipped
- *
- * @private
- * @function isSkipSeries
- *
- * @param {Highcharts.Series} series
- *
- * @return {boolean}
- */
- function isSkipSeries(series) {
- var a11yOptions = series.chart.options.accessibility;
- return series.options.skipKeyboardNavigation ||
- series.options.enableMouseTracking === false || // #8440
- !series.visible ||
- // Skip all points in a series where pointDescriptionThreshold is
- // reached
- (a11yOptions.pointDescriptionThreshold &&
- a11yOptions.pointDescriptionThreshold <= series.points.length);
- }
- /**
- * Determine if a point should be skipped
- *
- * @private
- * @function isSkipPoint
- *
- * @param {Highcharts.Point} point
- *
- * @return {boolean}
- */
- function isSkipPoint(point) {
- var a11yOptions = point.series.chart.options.accessibility;
- return point.isNull && a11yOptions.keyboardNavigation.skipNullPoints ||
- point.visible === false ||
- isSkipSeries(point.series);
- }
- /**
- * Get the point in a series that is closest (in distance) to a reference point.
- * Optionally supply weight factors for x and y directions.
- *
- * @private
- * @function getClosestPoint
- *
- * @param {Highcharts.Point} point
- *
- * @param {Highcharts.Series} series
- *
- * @param {number} [xWeight]
- *
- * @param {number} [yWeight]
- *
- * @return {Highcharts.Point|undefined}
- */
- function getClosestPoint(point, series, xWeight, yWeight) {
- var minDistance = Infinity,
- dPoint,
- minIx,
- distance,
- i = series.points.length;
- if (point.plotX === undefined || point.plotY === undefined) {
- return;
- }
- while (i--) {
- dPoint = series.points[i];
- if (dPoint.plotX === undefined || dPoint.plotY === undefined) {
- continue;
- }
- distance = (point.plotX - dPoint.plotX) *
- (point.plotX - dPoint.plotX) * (xWeight || 1) +
- (point.plotY - dPoint.plotY) *
- (point.plotY - dPoint.plotY) * (yWeight || 1);
- if (distance < minDistance) {
- minDistance = distance;
- minIx = i;
- }
- }
- return minIx !== undefined && series.points[minIx];
- }
- /**
- * Pan along axis in a direction (1 or -1), optionally with a defined
- * granularity (number of steps it takes to walk across current view)
- *
- * @private
- * @function Highcharts.Axis#panStep
- *
- * @param {number} direction
- *
- * @param {number} [granularity]
- */
- H.Axis.prototype.panStep = function (direction, granularity) {
- var gran = granularity || 3,
- extremes = this.getExtremes(),
- step = (extremes.max - extremes.min) / gran * direction,
- newMax = extremes.max + step,
- newMin = extremes.min + step,
- size = newMax - newMin;
- if (direction < 0 && newMin < extremes.dataMin) {
- newMin = extremes.dataMin;
- newMax = newMin + size;
- } else if (direction > 0 && newMax > extremes.dataMax) {
- newMax = extremes.dataMax;
- newMin = newMax - size;
- }
- this.setExtremes(newMin, newMax);
- };
- /**
- * Set chart's focus to an SVGElement. Calls focus() on it, and draws the focus
- * border.
- *
- * @private
- * @function Highcharts.Chart#setFocusToElement
- *
- * @param {Highcharts.SVGElement} svgElement
- * Element to draw the border around.
- *
- * @param {Highcharts.SVGElement} [focusElement]
- * If supplied, it draws the border around svgElement and sets the focus
- * to focusElement.
- */
- H.Chart.prototype.setFocusToElement = function (svgElement, focusElement) {
- var focusBorderOptions = this.options.accessibility
- .keyboardNavigation.focusBorder,
- browserFocusElement = focusElement || svgElement;
- // Set browser focus if possible
- if (
- browserFocusElement.element &&
- browserFocusElement.element.focus
- ) {
- browserFocusElement.element.focus();
- // Hide default focus ring
- if (focusBorderOptions.hideBrowserFocusOutline) {
- browserFocusElement.css({ outline: 'none' });
- }
- }
- if (focusBorderOptions.enabled) {
- // Remove old focus border
- if (this.focusElement) {
- this.focusElement.removeFocusBorder();
- }
- // Draw focus border (since some browsers don't do it automatically)
- svgElement.addFocusBorder(focusBorderOptions.margin, {
- stroke: focusBorderOptions.style.color,
- strokeWidth: focusBorderOptions.style.lineWidth,
- borderRadius: focusBorderOptions.style.borderRadius
- });
- this.focusElement = svgElement;
- }
- };
- /**
- * Highlights a point (show tooltip and display hover state).
- *
- * @private
- * @function Highcharts.Point#highlight
- *
- * @return {Highcharts.Point}
- * This highlighted point.
- */
- H.Point.prototype.highlight = function () {
- var chart = this.series.chart;
- if (!this.isNull) {
- this.onMouseOver(); // Show the hover marker and tooltip
- } else {
- if (chart.tooltip) {
- chart.tooltip.hide(0);
- }
- // Don't call blur on the element, as it messes up the chart div's focus
- }
- // We focus only after calling onMouseOver because the state change can
- // change z-index and mess up the element.
- if (this.graphic) {
- chart.setFocusToElement(this.graphic);
- }
- chart.highlightedPoint = this;
- return this;
- };
- /**
- * Function to highlight next/previous point in chart.
- *
- * @private
- * @function Highcharts.Chart#highlightAdjacentPoint
- *
- * @param {boolean} next
- * Flag for the direction.
- *
- * @return {Highcharts.Point|false}
- * Returns highlighted point on success, false on failure (no adjacent
- * point to highlight in chosen direction).
- */
- H.Chart.prototype.highlightAdjacentPoint = function (next) {
- var chart = this,
- series = chart.series,
- curPoint = chart.highlightedPoint,
- curPointIndex = curPoint && getPointIndex(curPoint) || 0,
- curPoints = curPoint && curPoint.series.points,
- lastSeries = chart.series && chart.series[chart.series.length - 1],
- lastPoint = lastSeries && lastSeries.points &&
- lastSeries.points[lastSeries.points.length - 1],
- newSeries,
- newPoint;
- // If no points, return false
- if (!series[0] || !series[0].points) {
- return false;
- }
- if (!curPoint) {
- // No point is highlighted yet. Try first/last point depending on move
- // direction
- newPoint = next ? series[0].points[0] : lastPoint;
- } else {
- // We have a highlighted point.
- // Grab next/prev point & series
- newSeries = series[curPoint.series.index + (next ? 1 : -1)];
- newPoint = curPoints[curPointIndex + (next ? 1 : -1)];
- if (!newPoint && newSeries) {
- // Done with this series, try next one
- newPoint = newSeries.points[next ? 0 : newSeries.points.length - 1];
- }
- // If there is no adjacent point, we return false
- if (!newPoint) {
- return false;
- }
- }
- // Recursively skip points
- if (isSkipPoint(newPoint)) {
- // If we skip this whole series, move to the end of the series before we
- // recurse, just to optimize
- newSeries = newPoint.series;
- if (isSkipSeries(newSeries)) {
- chart.highlightedPoint = next ?
- newSeries.points[newSeries.points.length - 1] :
- newSeries.points[0];
- } else {
- // Otherwise, just move one point
- chart.highlightedPoint = newPoint;
- }
- // Retry
- return chart.highlightAdjacentPoint(next);
- }
- // There is an adjacent point, highlight it
- return newPoint.highlight();
- };
- /**
- * Highlight first valid point in a series. Returns the point if successfully
- * highlighted, otherwise false. If there is a highlighted point in the series,
- * use that as starting point.
- *
- * @private
- * @function Highcharts.Series#highlightFirstValidPoint
- *
- * @return {Highcharts.Point|false}
- */
- H.Series.prototype.highlightFirstValidPoint = function () {
- var curPoint = this.chart.highlightedPoint,
- start = (curPoint && curPoint.series) === this ?
- getPointIndex(curPoint) :
- 0,
- points = this.points;
- if (points) {
- for (var i = start, len = points.length; i < len; ++i) {
- if (!isSkipPoint(points[i])) {
- return points[i].highlight();
- }
- }
- for (var j = start; j >= 0; --j) {
- if (!isSkipPoint(points[j])) {
- return points[j].highlight();
- }
- }
- }
- return false;
- };
- /**
- * Highlight next/previous series in chart. Returns false if no adjacent series
- * in the direction, otherwise returns new highlighted point.
- *
- * @private
- * @function Highcharts.Chart#highlightAdjacentSeries
- *
- * @param {boolean} down
- *
- * @return {Highcharts.Point|false}
- */
- H.Chart.prototype.highlightAdjacentSeries = function (down) {
- var chart = this,
- newSeries,
- newPoint,
- adjacentNewPoint,
- curPoint = chart.highlightedPoint,
- lastSeries = chart.series && chart.series[chart.series.length - 1],
- lastPoint = lastSeries && lastSeries.points &&
- lastSeries.points[lastSeries.points.length - 1];
- // If no point is highlighted, highlight the first/last point
- if (!chart.highlightedPoint) {
- newSeries = down ? (chart.series && chart.series[0]) : lastSeries;
- newPoint = down ?
- (newSeries && newSeries.points && newSeries.points[0]) : lastPoint;
- return newPoint ? newPoint.highlight() : false;
- }
- newSeries = chart.series[curPoint.series.index + (down ? -1 : 1)];
- if (!newSeries) {
- return false;
- }
- // We have a new series in this direction, find the right point
- // Weigh xDistance as counting much higher than Y distance
- newPoint = getClosestPoint(curPoint, newSeries, 4);
- if (!newPoint) {
- return false;
- }
- // New series and point exists, but we might want to skip it
- if (isSkipSeries(newSeries)) {
- // Skip the series
- newPoint.highlight();
- adjacentNewPoint = chart.highlightAdjacentSeries(down); // Try recurse
- if (!adjacentNewPoint) {
- // Recurse failed
- curPoint.highlight();
- return false;
- }
- // Recurse succeeded
- return adjacentNewPoint;
- }
- // Highlight the new point or any first valid point back or forwards from it
- newPoint.highlight();
- return newPoint.series.highlightFirstValidPoint();
- };
- /**
- * Highlight the closest point vertically.
- *
- * @private
- * @function Highcharts.Chart#highlightAdjacentPointVertical
- *
- * @param {boolean} down
- *
- * @return {Highcharts.Point|false}
- */
- H.Chart.prototype.highlightAdjacentPointVertical = function (down) {
- var curPoint = this.highlightedPoint,
- minDistance = Infinity,
- bestPoint;
- if (curPoint.plotX === undefined || curPoint.plotY === undefined) {
- return false;
- }
- this.series.forEach(function (series) {
- if (isSkipSeries(series)) {
- return;
- }
- series.points.forEach(function (point) {
- if (point.plotY === undefined || point.plotX === undefined ||
- point === curPoint) {
- return;
- }
- var yDistance = point.plotY - curPoint.plotY,
- width = Math.abs(point.plotX - curPoint.plotX),
- distance = Math.abs(yDistance) * Math.abs(yDistance) +
- width * width * 4; // Weigh horizontal distance highly
- // Reverse distance number if axis is reversed
- if (series.yAxis.reversed) {
- yDistance *= -1;
- }
- if (
- yDistance < 0 && down || yDistance > 0 && !down || // Wrong dir
- distance < 5 || // Points in same spot => infinite loop
- isSkipPoint(point)
- ) {
- return;
- }
- if (distance < minDistance) {
- minDistance = distance;
- bestPoint = point;
- }
- });
- });
- return bestPoint ? bestPoint.highlight() : false;
- };
- /**
- * Show the export menu and focus the first item (if exists).
- *
- * @private
- * @function Highcharts.Chart#showExportMenu
- */
- H.Chart.prototype.showExportMenu = function () {
- if (this.exportSVGElements && this.exportSVGElements[0]) {
- this.exportSVGElements[0].element.onclick();
- this.highlightExportItem(0);
- }
- };
- /**
- * Hide export menu.
- *
- * @private
- * @function Highcharts.Chart#hideExportMenu
- */
- H.Chart.prototype.hideExportMenu = function () {
- var chart = this,
- exportList = chart.exportDivElements;
- if (exportList && chart.exportContextMenu) {
- // Reset hover states etc.
- exportList.forEach(function (el) {
- if (el.className === 'highcharts-menu-item' && el.onmouseout) {
- el.onmouseout();
- }
- });
- chart.highlightedExportItem = 0;
- // Hide the menu div
- chart.exportContextMenu.hideMenu();
- // Make sure the chart has focus and can capture keyboard events
- chart.container.focus();
- }
- };
- /**
- * Highlight export menu item by index.
- *
- * @private
- * @function Highcharts.Chart#highlightExportItem
- *
- * @param {number} ix
- *
- * @return {true|undefined}
- */
- H.Chart.prototype.highlightExportItem = function (ix) {
- var listItem = this.exportDivElements && this.exportDivElements[ix],
- curHighlighted =
- this.exportDivElements &&
- this.exportDivElements[this.highlightedExportItem],
- hasSVGFocusSupport;
- if (
- listItem &&
- listItem.tagName === 'DIV' &&
- !(listItem.children && listItem.children.length)
- ) {
- // Test if we have focus support for SVG elements
- hasSVGFocusSupport = !!(
- this.renderTo.getElementsByTagName('g')[0] || {}
- ).focus;
- // Only focus if we can set focus back to the elements after
- // destroying the menu (#7422)
- if (listItem.focus && hasSVGFocusSupport) {
- listItem.focus();
- }
- if (curHighlighted && curHighlighted.onmouseout) {
- curHighlighted.onmouseout();
- }
- if (listItem.onmouseover) {
- listItem.onmouseover();
- }
- this.highlightedExportItem = ix;
- return true;
- }
- };
- /**
- * Try to highlight the last valid export menu item.
- *
- * @private
- * @function Highcharts.Chart#highlightLastExportItem
- */
- H.Chart.prototype.highlightLastExportItem = function () {
- var chart = this,
- i;
- if (chart.exportDivElements) {
- i = chart.exportDivElements.length;
- while (i--) {
- if (chart.highlightExportItem(i)) {
- break;
- }
- }
- }
- };
- /**
- * Highlight range selector button by index.
- *
- * @private
- * @function Highcharts.Chart#highlightRangeSelectorButton
- *
- * @param {number} ix
- *
- * @return {boolean}
- */
- H.Chart.prototype.highlightRangeSelectorButton = function (ix) {
- var buttons = this.rangeSelector.buttons;
- // Deselect old
- if (buttons[this.highlightedRangeSelectorItemIx]) {
- buttons[this.highlightedRangeSelectorItemIx].setState(
- this.oldRangeSelectorItemState || 0
- );
- }
- // Select new
- this.highlightedRangeSelectorItemIx = ix;
- if (buttons[ix]) {
- this.setFocusToElement(buttons[ix].box, buttons[ix]);
- this.oldRangeSelectorItemState = buttons[ix].state;
- buttons[ix].setState(2);
- return true;
- }
- return false;
- };
- /**
- * Highlight legend item by index.
- *
- * @private
- * @function Highcharts.Chart#highlightLegendItem
- *
- * @param {number} ix
- *
- * @return {boolean}
- */
- H.Chart.prototype.highlightLegendItem = function (ix) {
- var items = this.legend.allItems,
- oldIx = this.highlightedLegendItemIx;
- if (items[ix]) {
- if (items[oldIx]) {
- fireEvent(
- items[oldIx].legendGroup.element,
- 'mouseout'
- );
- }
- // Scroll if we have to
- if (items[ix].pageIx !== undefined &&
- items[ix].pageIx + 1 !== this.legend.currentPage) {
- this.legend.scroll(1 + items[ix].pageIx - this.legend.currentPage);
- }
- // Focus
- this.highlightedLegendItemIx = ix;
- this.setFocusToElement(items[ix].legendItem, items[ix].legendGroup);
- fireEvent(items[ix].legendGroup.element, 'mouseover');
- return true;
- }
- return false;
- };
- /**
- * Add keyboard navigation handling modules to chart.
- *
- * @private
- * @function Highcharts.Chart#addKeyboardNavigationModules
- */
- H.Chart.prototype.addKeyboardNavigationModules = function () {
- var chart = this;
- /**
- * @private
- * @function navModuleFactory
- *
- * @param {string} id
- *
- * @param {Array<Array<number>,Function>} keyMap
- *
- * @param {Highcharts.Dictionary<Function>} options
- *
- * @return {KeyboardNavigationModule}
- */
- function navModuleFactory(id, keyMap, options) {
- return new KeyboardNavigationModule(chart, merge({
- keyCodeMap: keyMap
- }, { id: id }, options));
- }
- /**
- * List of the different keyboard handling modes we use depending on where
- * we are in the chart. Each mode has a set of handling functions mapped to
- * key codes. Each mode determines when to move to the next/prev mode.
- *
- * @private
- * @name Highcharts.Chart#keyboardNavigationModules
- * @type {Array<KeyboardNavigationModule>}
- */
- chart.keyboardNavigationModules = [
- // Entry point catching the first tab, allowing users to tab into points
- // more intuitively.
- navModuleFactory('entry', []),
- // Points
- navModuleFactory('points', [
- // Left/Right
- [[37, 39], function (keyCode) {
- var right = keyCode === 39;
- if (!chart.highlightAdjacentPoint(right)) {
- // Failed to highlight next, wrap to last/first
- return this.init(right ? 1 : -1);
- }
- return true;
- }],
- // Up/Down
- [[38, 40], function (keyCode) {
- var down = keyCode !== 38,
- navOptions = chart.options.accessibility.keyboardNavigation;
- if (navOptions.mode && navOptions.mode === 'serialize') {
- // Act like left/right
- if (!chart.highlightAdjacentPoint(down)) {
- return this.init(down ? 1 : -1);
- }
- return true;
- }
- // Normal mode, move between series
- var highlightMethod = chart.highlightedPoint &&
- chart.highlightedPoint.series.keyboardMoveVertical ?
- 'highlightAdjacentPointVertical' :
- 'highlightAdjacentSeries';
- chart[highlightMethod](down);
- return true;
- }],
- // Enter/Spacebar
- [[13, 32], function () {
- if (chart.highlightedPoint) {
- chart.highlightedPoint.firePointEvent('click');
- }
- }]
- ], {
- // Always start highlighting from scratch when entering this module
- init: function (dir) {
- var numSeries = chart.series.length,
- i = dir > 0 ? 0 : numSeries,
- res;
- if (dir > 0) {
- delete chart.highlightedPoint;
- // Find first valid point to highlight
- while (i < numSeries) {
- res = chart.series[i].highlightFirstValidPoint();
- if (res) {
- return res;
- }
- ++i;
- }
- } else {
- // Find last valid point to highlight
- while (i--) {
- chart.highlightedPoint = chart.series[i].points[
- chart.series[i].points.length - 1
- ];
- // Highlight first valid point in the series will also
- // look backwards. It always starts from currently
- // highlighted point.
- res = chart.series[i].highlightFirstValidPoint();
- if (res) {
- return res;
- }
- }
- }
- },
- // If leaving points, don't show tooltip anymore
- terminate: function () {
- if (chart.tooltip) {
- chart.tooltip.hide(0);
- }
- delete chart.highlightedPoint;
- }
- }),
- // Exporting
- navModuleFactory('exporting', [
- // Left/Up
- [[37, 38], function () {
- var i = chart.highlightedExportItem || 0,
- reachedEnd = true;
- // Try to highlight prev item in list. Highlighting e.g.
- // separators will fail.
- while (i--) {
- if (chart.highlightExportItem(i)) {
- reachedEnd = false;
- break;
- }
- }
- if (reachedEnd) {
- chart.highlightLastExportItem();
- return true;
- }
- }],
- // Right/Down
- [[39, 40], function () {
- var highlightedExportItem = chart.highlightedExportItem || 0,
- reachedEnd = true;
- // Try to highlight next item in list. Highlighting e.g.
- // separators will fail.
- for (
- var i = highlightedExportItem + 1;
- i < chart.exportDivElements.length;
- ++i
- ) {
- if (chart.highlightExportItem(i)) {
- reachedEnd = false;
- break;
- }
- }
- if (reachedEnd) {
- chart.highlightExportItem(0);
- return true;
- }
- }],
- // Enter/Spacebar
- [[13, 32], function () {
- fakeClickEvent(
- chart.exportDivElements[chart.highlightedExportItem]
- );
- }]
- ], {
- // Only run exporting navigation if exporting support exists and is
- // enabled on chart
- validate: function () {
- return (
- chart.exportChart &&
- !(
- chart.options.exporting &&
- chart.options.exporting.enabled === false
- )
- );
- },
- // Show export menu
- init: function (direction) {
- chart.highlightedPoint = null;
- chart.showExportMenu();
- // If coming back to export menu from other module, try to
- // highlight last item in menu
- if (direction < 0) {
- chart.highlightLastExportItem();
- }
- },
- // Hide the menu
- terminate: function () {
- chart.hideExportMenu();
- }
- }),
- // Map zoom
- navModuleFactory('mapZoom', [
- // Up/down/left/right
- [[38, 40, 37, 39], function (keyCode) {
- chart[keyCode === 38 || keyCode === 40 ? 'yAxis' : 'xAxis'][0]
- .panStep(keyCode < 39 ? -1 : 1);
- }],
- // Tabs
- [[9], function (keyCode, e) {
- var button;
- // Deselect old
- chart.mapNavButtons[chart.focusedMapNavButtonIx].setState(0);
- if (
- e.shiftKey && !chart.focusedMapNavButtonIx ||
- !e.shiftKey && chart.focusedMapNavButtonIx
- ) { // trying to go somewhere we can't?
- chart.mapZoom(); // Reset zoom
- // Nowhere to go, go to prev/next module
- return this.move(e.shiftKey ? -1 : 1);
- }
- chart.focusedMapNavButtonIx += e.shiftKey ? -1 : 1;
- button = chart.mapNavButtons[chart.focusedMapNavButtonIx];
- chart.setFocusToElement(button.box, button);
- button.setState(2);
- }],
- // Enter/Spacebar
- [[13, 32], function () {
- fakeClickEvent(
- chart.mapNavButtons[chart.focusedMapNavButtonIx].element
- );
- }]
- ], {
- // Only run this module if we have map zoom on the chart
- validate: function () {
- return (
- chart.mapZoom &&
- chart.mapNavButtons &&
- chart.mapNavButtons.length === 2
- );
- },
- // Make zoom buttons do their magic
- init: function (direction) {
- var zoomIn = chart.mapNavButtons[0],
- zoomOut = chart.mapNavButtons[1],
- initialButton = direction > 0 ? zoomIn : zoomOut;
- chart.mapNavButtons.forEach(function (button, i) {
- button.element.setAttribute('tabindex', -1);
- button.element.setAttribute('role', 'button');
- button.element.setAttribute(
- 'aria-label',
- chart.langFormat(
- 'accessibility.mapZoom' + (i ? 'Out' : 'In'),
- { chart: chart }
- )
- );
- });
- chart.setFocusToElement(initialButton.box, initialButton);
- initialButton.setState(2);
- chart.focusedMapNavButtonIx = direction > 0 ? 0 : 1;
- }
- }),
- // Highstock range selector (minus input boxes)
- navModuleFactory('rangeSelector', [
- // Left/Right/Up/Down
- [[37, 39, 38, 40], function (keyCode) {
- var direction = (keyCode === 37 || keyCode === 38) ? -1 : 1;
- // Try to highlight next/prev button
- if (
- !chart.highlightRangeSelectorButton(
- chart.highlightedRangeSelectorItemIx + direction
- )
- ) {
- return this.move(direction);
- }
- }],
- // Enter/Spacebar
- [[13, 32], function () {
- // Don't allow click if button used to be disabled
- if (chart.oldRangeSelectorItemState !== 3) {
- fakeClickEvent(
- chart.rangeSelector.buttons[
- chart.highlightedRangeSelectorItemIx
- ].element
- );
- }
- }]
- ], {
- // Only run this module if we have range selector
- validate: function () {
- return (
- chart.rangeSelector &&
- chart.rangeSelector.buttons &&
- chart.rangeSelector.buttons.length
- );
- },
- // Make elements focusable and accessible
- init: function (direction) {
- chart.rangeSelector.buttons.forEach(function (button) {
- button.element.setAttribute('tabindex', '-1');
- button.element.setAttribute('role', 'button');
- button.element.setAttribute(
- 'aria-label',
- chart.langFormat(
- 'accessibility.rangeSelectorButton',
- {
- chart: chart,
- buttonText: button.text && button.text.textStr
- }
- )
- );
- });
- // Focus first/last button
- chart.highlightRangeSelectorButton(
- direction > 0 ? 0 : chart.rangeSelector.buttons.length - 1
- );
- }
- }),
- // Highstock range selector, input boxes
- navModuleFactory('rangeSelectorInput', [
- // Tab/Up/Down
- [[9, 38, 40], function (keyCode, e) {
- var direction =
- (keyCode === 9 && e.shiftKey || keyCode === 38) ? -1 : 1,
- newIx = chart.highlightedInputRangeIx =
- chart.highlightedInputRangeIx + direction;
- // Try to highlight next/prev item in list.
- if (newIx > 1 || newIx < 0) { // Out of range
- return this.move(direction);
- }
- chart.rangeSelector[newIx ? 'maxInput' : 'minInput'].focus();
- }]
- ], {
- // Only run if we have range selector with input boxes
- validate: function () {
- var inputVisible = (
- chart.rangeSelector &&
- chart.rangeSelector.inputGroup &&
- chart.rangeSelector.inputGroup.element
- .getAttribute('visibility') !== 'hidden'
- );
- return (
- inputVisible &&
- chart.options.rangeSelector.inputEnabled !== false &&
- chart.rangeSelector.minInput &&
- chart.rangeSelector.maxInput
- );
- },
- // Highlight first/last input box
- init: function (direction) {
- chart.highlightedInputRangeIx = direction > 0 ? 0 : 1;
- chart.rangeSelector[
- chart.highlightedInputRangeIx ? 'maxInput' : 'minInput'
- ].focus();
- }
- }),
- // Legend navigation
- navModuleFactory('legend', [
- // Left/Right/Up/Down
- [[37, 39, 38, 40], function (keyCode) {
- var direction = (keyCode === 37 || keyCode === 38) ? -1 : 1;
- // Try to highlight next/prev legend item
- if (!chart.highlightLegendItem(
- chart.highlightedLegendItemIx + direction
- ) && chart.legend.allItems.length > 1) {
- // Wrap around if more than 1 item
- this.init(direction);
- }
- }],
- // Enter/Spacebar
- [[13, 32], function () {
- var legendElement = chart.legend.allItems[
- chart.highlightedLegendItemIx
- ].legendItem.element;
- fakeClickEvent(
- !chart.legend.options.useHTML ? // #8561
- legendElement.parentNode : legendElement
- );
- }]
- ], {
- // Only run this module if we have at least one legend - wait for
- // it - item. Don't run if the legend is populated by a colorAxis.
- // Don't run if legend navigation is disabled.
- validate: function () {
- return chart.legend && chart.legend.allItems &&
- chart.legend.display &&
- !(chart.colorAxis && chart.colorAxis.length) &&
- (chart.options.legend &&
- chart.options.legend.keyboardNavigation &&
- chart.options.legend.keyboardNavigation.enabled) !== false;
- },
- // Make elements focusable and accessible
- init: function (direction) {
- chart.legend.allItems.forEach(function (item) {
- item.legendGroup.element.setAttribute('tabindex', '-1');
- item.legendGroup.element.setAttribute('role', 'button');
- item.legendGroup.element.setAttribute(
- 'aria-label',
- chart.langFormat(
- 'accessibility.legendItem',
- {
- chart: chart,
- itemName: stripTags(item.name)
- }
- )
- );
- });
- // Focus first/last item
- chart.highlightLegendItem(
- direction > 0 ? 0 : chart.legend.allItems.length - 1
- );
- }
- })
- ];
- };
- /**
- * Add exit anchor to the chart. We use this to move focus out of chart whenever
- * we want, by setting focus to this div and not preventing the default tab
- * action. We also use this when users come back into the chart by tabbing back,
- * in order to navigate from the end of the chart.
- *
- * @private
- * @function Highcharts.Chart#addExitAnchor
- *
- * @return {Function}
- * Returns the unbind function for the exit anchor's event handler.
- */
- H.Chart.prototype.addExitAnchor = function () {
- var chart = this;
- chart.tabExitAnchor = doc.createElement('div');
- chart.tabExitAnchor.setAttribute('tabindex', '0');
- // Hide exit anchor
- merge(true, chart.tabExitAnchor.style, {
- position: 'absolute',
- left: '-9999px',
- top: 'auto',
- width: '1px',
- height: '1px',
- overflow: 'hidden'
- });
- chart.renderTo.appendChild(chart.tabExitAnchor);
- return addEvent(
- chart.tabExitAnchor,
- 'focus',
- function (ev) {
- var e = ev || win.event,
- curModule;
- // If focusing and we are exiting, do nothing once.
- if (!chart.exiting) {
- // Not exiting, means we are coming in backwards
- chart.renderTo.focus();
- e.preventDefault();
- // Move to last valid keyboard nav module
- // Note the we don't run it, just set the index
- chart.keyboardNavigationModuleIndex =
- chart.keyboardNavigationModules.length - 1;
- curModule = chart.keyboardNavigationModules[
- chart.keyboardNavigationModuleIndex
- ];
- // Validate the module
- if (curModule.validate && !curModule.validate()) {
- // Invalid.
- // Move inits next valid module in direction
- curModule.move(-1);
- } else {
- // We have a valid module, init it
- curModule.init(-1);
- }
- } else {
- // Don't skip the next focus, we only skip once.
- chart.exiting = false;
- }
- }
- );
- };
- /**
- * Clear the chart and reset the navigation state.
- *
- * @private
- * @function Highcharts.Chart#resetKeyboardNavigation
- */
- H.Chart.prototype.resetKeyboardNavigation = function () {
- var chart = this,
- curMod = (
- chart.keyboardNavigationModules &&
- chart.keyboardNavigationModules[
- chart.keyboardNavigationModuleIndex || 0
- ]
- );
- if (curMod && curMod.terminate) {
- curMod.terminate();
- }
- if (chart.focusElement) {
- chart.focusElement.removeFocusBorder();
- }
- chart.keyboardNavigationModuleIndex = 0;
- chart.keyboardReset = true;
- };
- // On destroy, we need to clean up the focus border and the state.
- H.addEvent(H.Series, 'destroy', function () {
- var chart = this.chart;
- if (chart.highlightedPoint && chart.highlightedPoint.series === this) {
- delete chart.highlightedPoint;
- if (chart.focusElement) {
- chart.focusElement.removeFocusBorder();
- }
- }
- });
- // Add keyboard navigation events on chart load.
- H.Chart.prototype.callbacks.push(function (chart) {
- var a11yOptions = chart.options.accessibility;
- if (a11yOptions.enabled && a11yOptions.keyboardNavigation.enabled) {
- // Init nav modules. We start at the first module, and as the user
- // navigates through the chart the index will increase to use different
- // handler modules.
- chart.addKeyboardNavigationModules();
- chart.keyboardNavigationModuleIndex = 0;
- // Make chart container reachable by tab
- if (
- chart.container.hasAttribute &&
- !chart.container.hasAttribute('tabIndex')
- ) {
- chart.container.setAttribute('tabindex', '0');
- }
- // Add tab exit anchor
- if (!chart.tabExitAnchor) {
- chart.unbindExitAnchorFocus = chart.addExitAnchor();
- }
- // Handle keyboard events by routing them to active keyboard nav module
- chart.unbindKeydownHandler = addEvent(chart.renderTo, 'keydown',
- function (ev) {
- var e = ev || win.event,
- curNavModule = chart.keyboardNavigationModules[
- chart.keyboardNavigationModuleIndex
- ];
- chart.keyboardReset = false;
- // If there is a nav module for the current index, run it.
- // Otherwise, we are outside of the chart in some direction.
- if (curNavModule) {
- if (curNavModule.run(e)) {
- // Successfully handled this key event, stop default
- e.preventDefault();
- }
- }
- });
- // Reset chart navigation state if we click outside the chart and it's
- // not already reset
- chart.unbindBlurHandler = addEvent(doc, 'mouseup', function () {
- if (
- !chart.keyboardReset &&
- !(chart.pointer && chart.pointer.chartPosition)
- ) {
- chart.resetKeyboardNavigation();
- }
- });
- // Add cleanup handlers
- addEvent(chart, 'destroy', function () {
- chart.resetKeyboardNavigation();
- if (chart.unbindExitAnchorFocus && chart.tabExitAnchor) {
- chart.unbindExitAnchorFocus();
- }
- if (chart.unbindKeydownHandler && chart.renderTo) {
- chart.unbindKeydownHandler();
- }
- if (chart.unbindBlurHandler) {
- chart.unbindBlurHandler();
- }
- });
- }
- });
- }(Highcharts));
- return (function () {
- }());
- }));
|