12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408 |
- /**
- * @license Highcharts JS v9.1.1 (2021-06-04)
- *
- * Accessibility module
- *
- * (c) 2010-2021 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('highcharts/modules/accessibility', ['highcharts'], function (Highcharts) {
- factory(Highcharts);
- factory.Highcharts = Highcharts;
- return factory;
- });
- } else {
- factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
- }
- }(function (Highcharts) {
- var _modules = Highcharts ? Highcharts._modules : {};
- function _registerModule(obj, path, args, fn) {
- if (!obj.hasOwnProperty(path)) {
- obj[path] = fn.apply(null, args);
- }
- }
- _registerModule(_modules, 'Accessibility/Utils/HTMLUtilities.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Utility functions for accessibility module.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var doc = H.doc,
- win = H.win;
- var merge = U.merge;
- /* eslint-disable valid-jsdoc */
- /**
- * @private
- * @param {Highcharts.HTMLDOMElement} el
- * @param {string} className
- * @return {void}
- */
- function addClass(el, className) {
- if (el.classList) {
- el.classList.add(className);
- }
- else if (el.className.indexOf(className) < 0) {
- // Note: Dumb check for class name exists, should be fine for practical
- // use cases, but will return false positives if the element has a class
- // that contains the className.
- el.className += className;
- }
- }
- /**
- * @private
- * @param {string} str
- * @return {string}
- */
- function escapeStringForHTML(str) {
- return str
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''')
- .replace(/\//g, '/');
- }
- /**
- * Get an element by ID
- * @param {string} id
- * @private
- * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|null}
- */
- function getElement(id) {
- return doc.getElementById(id);
- }
- /**
- * Get a fake mouse event of a given type
- * @param {string} type
- * @private
- * @return {global.MouseEvent}
- */
- function getFakeMouseEvent(type) {
- if (typeof win.MouseEvent === 'function') {
- return new win.MouseEvent(type);
- }
- // No MouseEvent support, try using initMouseEvent
- if (doc.createEvent) {
- var evt = doc.createEvent('MouseEvent');
- if (evt.initMouseEvent) {
- evt.initMouseEvent(type, true, // Bubble
- true, // Cancel
- win, // View
- type === 'click' ? 1 : 0, // Detail
- // Coords
- 0, 0, 0, 0,
- // Pressed keys
- false, false, false, false, 0, // button
- null // related target
- );
- return evt;
- }
- }
- return { type: type };
- }
- /**
- * Get an appropriate heading level for an element. Corresponds to the
- * heading level below the previous heading in the DOM.
- *
- * Note: Only detects previous headings in the DOM that are siblings,
- * ancestors, or previous siblings of ancestors. Headings that are nested below
- * siblings of ancestors (cousins et.al) are not picked up. This is because it
- * is ambiguous whether or not the nesting is for layout purposes or indicates a
- * separate section.
- *
- * @private
- * @param {Highcharts.HTMLDOMElement} [element]
- * @return {string} The heading tag name (h1, h2 etc).
- * If no nearest heading is found, "p" is returned.
- */
- function getHeadingTagNameForElement(element) {
- var getIncreasedHeadingLevel = function (tagName) {
- var headingLevel = parseInt(tagName.slice(1), 10);
- var newLevel = Math.min(6,
- headingLevel + 1);
- return 'h' + newLevel;
- };
- var isHeading = function (tagName) { return /H[1-6]/.test(tagName); };
- var getPreviousSiblingsHeading = function (el) {
- var sibling = el;
- while (sibling = sibling.previousSibling) { // eslint-disable-line
- var tagName = sibling.tagName || '';
- if (isHeading(tagName)) {
- return tagName;
- }
- }
- return '';
- };
- var getHeadingRecursive = function (el) {
- var prevSiblingsHeading = getPreviousSiblingsHeading(el);
- if (prevSiblingsHeading) {
- return getIncreasedHeadingLevel(prevSiblingsHeading);
- }
- // No previous siblings are headings, try parent node
- var parent = el.parentElement;
- if (!parent) {
- return 'p';
- }
- var parentTagName = parent.tagName;
- if (isHeading(parentTagName)) {
- return getIncreasedHeadingLevel(parentTagName);
- }
- return getHeadingRecursive(parent);
- };
- return getHeadingRecursive(element);
- }
- /**
- * Remove an element from the DOM.
- * @private
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} [element]
- * @return {void}
- */
- function removeElement(element) {
- if (element && element.parentNode) {
- element.parentNode.removeChild(element);
- }
- }
- /**
- * Utility function. Reverses child nodes of a DOM element.
- * @private
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} node
- * @return {void}
- */
- function reverseChildNodes(node) {
- var i = node.childNodes.length;
- while (i--) {
- node.appendChild(node.childNodes[i]);
- }
- }
- /**
- * Set attributes on element. Set to null to remove attribute.
- * @private
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} el
- * @param {Highcharts.HTMLAttributes|Highcharts.SVGAttributes} attrs
- * @return {void}
- */
- function setElAttrs(el, attrs) {
- Object.keys(attrs).forEach(function (attr) {
- var val = attrs[attr];
- if (val === null) {
- el.removeAttribute(attr);
- }
- else {
- el.setAttribute(attr, val);
- }
- });
- }
- /**
- * Used for aria-label attributes, painting on a canvas will fail if the
- * text contains tags.
- * @private
- * @param {string} str
- * @return {string}
- */
- function stripHTMLTagsFromString(str) {
- return typeof str === 'string' ?
- str.replace(/<\/?[^>]+(>|$)/g, '') : str;
- }
- /**
- * Utility function for hiding an element visually, but still keeping it
- * available to screen reader users.
- * @private
- * @param {Highcharts.HTMLDOMElement} element
- * @return {void}
- */
- function visuallyHideElement(element) {
- var hiddenStyle = {
- position: 'absolute',
- width: '1px',
- height: '1px',
- overflow: 'hidden',
- whiteSpace: 'nowrap',
- clip: 'rect(1px, 1px, 1px, 1px)',
- marginTop: '-3px',
- '-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=1)',
- filter: 'alpha(opacity=1)',
- opacity: '0.01'
- };
- merge(true, element.style, hiddenStyle);
- }
- var HTMLUtilities = {
- addClass: addClass,
- escapeStringForHTML: escapeStringForHTML,
- getElement: getElement,
- getFakeMouseEvent: getFakeMouseEvent,
- getHeadingTagNameForElement: getHeadingTagNameForElement,
- removeElement: removeElement,
- reverseChildNodes: reverseChildNodes,
- setElAttrs: setElAttrs,
- stripHTMLTagsFromString: stripHTMLTagsFromString,
- visuallyHideElement: visuallyHideElement
- };
- return HTMLUtilities;
- });
- _registerModule(_modules, 'Accessibility/Utils/ChartUtilities.js', [_modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Core/Utilities.js']], function (HTMLUtilities, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Utils for dealing with charts.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString;
- var defined = U.defined,
- find = U.find,
- fireEvent = U.fireEvent;
- /* eslint-disable valid-jsdoc */
- /**
- * @return {string}
- */
- function getChartTitle(chart) {
- return stripHTMLTags(chart.options.title.text ||
- chart.langFormat('accessibility.defaultChartTitle', { chart: chart }));
- }
- /**
- * Return string with the axis name/title.
- * @param {Highcharts.Axis} axis
- * @return {string}
- */
- function getAxisDescription(axis) {
- return axis && (axis.userOptions && axis.userOptions.accessibility &&
- axis.userOptions.accessibility.description ||
- axis.axisTitle && axis.axisTitle.textStr ||
- axis.options.id ||
- axis.categories && 'categories' ||
- axis.dateTime && 'Time' ||
- 'values');
- }
- /**
- * Return string with text description of the axis range.
- * @param {Highcharts.Axis} axis The axis to get range desc of.
- * @return {string} A string with the range description for the axis.
- */
- function getAxisRangeDescription(axis) {
- var axisOptions = axis.options || {};
- // Handle overridden range description
- if (axisOptions.accessibility &&
- typeof axisOptions.accessibility.rangeDescription !== 'undefined') {
- return axisOptions.accessibility.rangeDescription;
- }
- // Handle category axes
- if (axis.categories) {
- return getCategoryAxisRangeDesc(axis);
- }
- // Use time range, not from-to?
- if (axis.dateTime && (axis.min === 0 || axis.dataMin === 0)) {
- return getAxisTimeLengthDesc(axis);
- }
- // Just use from and to.
- // We have the range and the unit to use, find the desc format
- return getAxisFromToDescription(axis);
- }
- /**
- * Describe the range of a category axis.
- * @param {Highcharts.Axis} axis
- * @return {string}
- */
- function getCategoryAxisRangeDesc(axis) {
- var chart = axis.chart;
- if (axis.dataMax && axis.dataMin) {
- return chart.langFormat('accessibility.axis.rangeCategories', {
- chart: chart,
- axis: axis,
- numCategories: axis.dataMax - axis.dataMin + 1
- });
- }
- return '';
- }
- /**
- * Describe the length of the time window shown on an axis.
- * @param {Highcharts.Axis} axis
- * @return {string}
- */
- function getAxisTimeLengthDesc(axis) {
- var chart = axis.chart;
- var range = {};
- var rangeUnit = 'Seconds';
- range.Seconds = ((axis.max || 0) - (axis.min || 0)) / 1000;
- range.Minutes = range.Seconds / 60;
- range.Hours = range.Minutes / 60;
- range.Days = range.Hours / 24;
- ['Minutes', 'Hours', 'Days'].forEach(function (unit) {
- if (range[unit] > 2) {
- rangeUnit = unit;
- }
- });
- var rangeValue = range[rangeUnit].toFixed(rangeUnit !== 'Seconds' &&
- rangeUnit !== 'Minutes' ? 1 : 0 // Use decimals for days/hours
- );
- // We have the range and the unit to use, find the desc format
- return chart.langFormat('accessibility.axis.timeRange' + rangeUnit, {
- chart: chart,
- axis: axis,
- range: rangeValue.replace('.0', '')
- });
- }
- /**
- * Describe an axis from-to range.
- * @param {Highcharts.Axis} axis
- * @return {string}
- */
- function getAxisFromToDescription(axis) {
- var chart = axis.chart;
- var dateRangeFormat = (chart.options &&
- chart.options.accessibility &&
- chart.options.accessibility.screenReaderSection.axisRangeDateFormat ||
- '');
- var format = function (axisKey) {
- return axis.dateTime ? chart.time.dateFormat(dateRangeFormat,
- axis[axisKey]) : axis[axisKey];
- };
- return chart.langFormat('accessibility.axis.rangeFromTo', {
- chart: chart,
- axis: axis,
- rangeFrom: format('min'),
- rangeTo: format('max')
- });
- }
- /**
- * Get the DOM element for the first point in the series.
- * @private
- * @param {Highcharts.Series} series
- * The series to get element for.
- * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|undefined}
- * The DOM element for the point.
- */
- function getSeriesFirstPointElement(series) {
- if (series.points && series.points.length) {
- var firstPointWithGraphic = find(series.points,
- function (p) { return !!p.graphic; });
- return (firstPointWithGraphic &&
- firstPointWithGraphic.graphic &&
- firstPointWithGraphic.graphic.element);
- }
- }
- /**
- * Get the DOM element for the series that we put accessibility info on.
- * @private
- * @param {Highcharts.Series} series
- * The series to get element for.
- * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|undefined}
- * The DOM element for the series
- */
- function getSeriesA11yElement(series) {
- var firstPointEl = getSeriesFirstPointElement(series);
- return (firstPointEl &&
- firstPointEl.parentNode || series.graph &&
- series.graph.element || series.group &&
- series.group.element); // Could be tracker series depending on series type
- }
- /**
- * Remove aria-hidden from element. Also unhides parents of the element, and
- * hides siblings that are not explicitly unhidden.
- * @private
- * @param {Highcharts.Chart} chart
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} element
- */
- function unhideChartElementFromAT(chart, element) {
- element.setAttribute('aria-hidden', false);
- if (element === chart.renderTo || !element.parentNode) {
- return;
- }
- // Hide siblings unless their hidden state is already explicitly set
- Array.prototype.forEach.call(element.parentNode.childNodes, function (node) {
- if (!node.hasAttribute('aria-hidden')) {
- node.setAttribute('aria-hidden', true);
- }
- });
- // Repeat for parent
- unhideChartElementFromAT(chart, element.parentNode);
- }
- /**
- * Hide series from screen readers.
- * @private
- * @param {Highcharts.Series} series
- * The series to hide
- * @return {void}
- */
- function hideSeriesFromAT(series) {
- var seriesEl = getSeriesA11yElement(series);
- if (seriesEl) {
- seriesEl.setAttribute('aria-hidden', true);
- }
- }
- /**
- * Get series objects by series name.
- * @private
- * @param {Highcharts.Chart} chart
- * @param {string} name
- * @return {Array<Highcharts.Series>}
- */
- function getSeriesFromName(chart, name) {
- if (!name) {
- return chart.series;
- }
- return (chart.series || []).filter(function (s) {
- return s.name === name;
- });
- }
- /**
- * Get point in a series from x/y values.
- * @private
- * @param {Array<Highcharts.Series>} series
- * @param {number} x
- * @param {number} y
- * @return {Highcharts.Point|undefined}
- */
- function getPointFromXY(series, x, y) {
- var i = series.length,
- res;
- while (i--) {
- res = find(series[i].points || [], function (p) {
- return p.x === x && p.y === y;
- });
- if (res) {
- return res;
- }
- }
- }
- /**
- * Get relative position of point on an x/y axis from 0 to 1.
- * @private
- * @param {Highcharts.Axis} axis
- * @param {Highcharts.Point} point
- * @return {number}
- */
- function getRelativePointAxisPosition(axis, point) {
- if (!defined(axis.dataMin) || !defined(axis.dataMax)) {
- return 0;
- }
- var axisStart = axis.toPixels(axis.dataMin);
- var axisEnd = axis.toPixels(axis.dataMax);
- // We have to use pixel position because of axis breaks, log axis etc.
- var positionProp = axis.coll === 'xAxis' ? 'x' : 'y';
- var pointPos = axis.toPixels(point[positionProp] || 0);
- return (pointPos - axisStart) / (axisEnd - axisStart);
- }
- /**
- * Get relative position of point on an x/y axis from 0 to 1.
- * @private
- * @param {Highcharts.Point} point
- */
- function scrollToPoint(point) {
- var xAxis = point.series.xAxis;
- var yAxis = point.series.yAxis;
- var axis = (xAxis && xAxis.scrollbar ? xAxis : yAxis);
- var scrollbar = (axis && axis.scrollbar);
- if (scrollbar && defined(scrollbar.to) && defined(scrollbar.from)) {
- var range = scrollbar.to - scrollbar.from;
- var pos = getRelativePointAxisPosition(axis,
- point);
- scrollbar.updatePosition(pos - range / 2, pos + range / 2);
- fireEvent(scrollbar, 'changed', {
- from: scrollbar.from,
- to: scrollbar.to,
- trigger: 'scrollbar',
- DOMEvent: null
- });
- }
- }
- var ChartUtilities = {
- getChartTitle: getChartTitle,
- getAxisDescription: getAxisDescription,
- getAxisRangeDescription: getAxisRangeDescription,
- getPointFromXY: getPointFromXY,
- getSeriesFirstPointElement: getSeriesFirstPointElement,
- getSeriesFromName: getSeriesFromName,
- getSeriesA11yElement: getSeriesA11yElement,
- unhideChartElementFromAT: unhideChartElementFromAT,
- hideSeriesFromAT: hideSeriesFromAT,
- scrollToPoint: scrollToPoint
- };
- return ChartUtilities;
- });
- _registerModule(_modules, 'Accessibility/KeyboardNavigationHandler.js', [_modules['Core/Utilities.js']], function (U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Keyboard navigation handler base class definition
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var find = U.find;
- /**
- * Options for the keyboard navigation handler.
- *
- * @interface Highcharts.KeyboardNavigationHandlerOptionsObject
- */ /**
- * An array containing pairs of an array of keycodes, mapped to a handler
- * function. When the keycode is received, the handler is called with the
- * keycode as parameter.
- * @name Highcharts.KeyboardNavigationHandlerOptionsObject#keyCodeMap
- * @type {Array<Array<Array<number>, Function>>}
- */ /**
- * Function to run on initialization of module.
- * @name Highcharts.KeyboardNavigationHandlerOptionsObject#init
- * @type {Function}
- */ /**
- * Function to run before moving to next/prev module. Receives moving direction
- * as parameter: +1 for next, -1 for previous.
- * @name Highcharts.KeyboardNavigationHandlerOptionsObject#terminate
- * @type {Function|undefined}
- */ /**
- * Function to run to validate module. Should return false if module should not
- * run, true otherwise. Receives chart as parameter.
- * @name Highcharts.KeyboardNavigationHandlerOptionsObject#validate
- * @type {Function|undefined}
- */
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * Define a keyboard navigation handler for use with a
- * Highcharts.AccessibilityComponent instance. This functions as an abstraction
- * layer for keyboard navigation, and defines a map of keyCodes to handler
- * functions.
- *
- * @requires module:modules/accessibility
- *
- * @sample highcharts/accessibility/custom-component
- * Custom accessibility component
- *
- * @class
- * @name Highcharts.KeyboardNavigationHandler
- *
- * @param {Highcharts.Chart} chart
- * The chart this module should act on.
- *
- * @param {Highcharts.KeyboardNavigationHandlerOptionsObject} options
- * Options for the keyboard navigation handler.
- */
- function KeyboardNavigationHandler(chart, options) {
- this.chart = chart;
- this.keyCodeMap = options.keyCodeMap || [];
- this.validate = options.validate;
- this.init = options.init;
- this.terminate = options.terminate;
- // Response enum
- this.response = {
- success: 1,
- prev: 2,
- next: 3,
- noHandler: 4,
- fail: 5 // Handler failed
- };
- }
- KeyboardNavigationHandler.prototype = {
- /**
- * Find handler function(s) for key code in the keyCodeMap and run it.
- *
- * @function KeyboardNavigationHandler#run
- * @param {global.KeyboardEvent} e
- * @return {number} Returns a response code indicating whether the run was
- * a success/fail/unhandled, or if we should move to next/prev module.
- */
- run: function (e) {
- var keyCode = e.which || e.keyCode;
- var response = this.response.noHandler;
- var handlerCodeSet = find(this.keyCodeMap,
- function (codeSet) {
- return codeSet[0].indexOf(keyCode) > -1;
- });
- if (handlerCodeSet) {
- response = handlerCodeSet[1].call(this, keyCode, e);
- }
- else if (keyCode === 9) {
- // Default tab handler, move to next/prev module
- response = this.response[e.shiftKey ? 'prev' : 'next'];
- }
- return response;
- }
- };
- return KeyboardNavigationHandler;
- });
- _registerModule(_modules, 'Accessibility/Utils/DOMElementProvider.js', [_modules['Core/Globals.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Core/Utilities.js']], function (H, HTMLUtilities, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Class that can keep track of elements added to DOM and clean them up on
- * destroy.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var doc = H.doc;
- var removeElement = HTMLUtilities.removeElement;
- var extend = U.extend;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * @private
- * @class
- */
- var DOMElementProvider = function () {
- this.elements = [];
- };
- extend(DOMElementProvider.prototype, {
- /**
- * Create an element and keep track of it for later removal.
- * Same args as document.createElement
- * @private
- */
- createElement: function () {
- var el = doc.createElement.apply(doc,
- arguments);
- this.elements.push(el);
- return el;
- },
- /**
- * Destroy all created elements, removing them from the DOM.
- * @private
- */
- destroyCreatedElements: function () {
- this.elements.forEach(function (element) {
- removeElement(element);
- });
- this.elements = [];
- }
- });
- return DOMElementProvider;
- });
- _registerModule(_modules, 'Accessibility/Utils/EventProvider.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Class that can keep track of events added, and clean them up on destroy.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var addEvent = U.addEvent,
- extend = U.extend;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * @private
- * @class
- */
- var EventProvider = function () {
- this.eventRemovers = [];
- };
- extend(EventProvider.prototype, {
- /**
- * Add an event to an element and keep track of it for later removal.
- * Same args as Highcharts.addEvent.
- * @private
- * @return {Function}
- */
- addEvent: function () {
- var remover = addEvent.apply(H,
- arguments);
- this.eventRemovers.push(remover);
- return remover;
- },
- /**
- * Remove all added events.
- * @private
- * @return {void}
- */
- removeAddedEvents: function () {
- this.eventRemovers.forEach(function (remover) {
- remover();
- });
- this.eventRemovers = [];
- }
- });
- return EventProvider;
- });
- _registerModule(_modules, 'Accessibility/AccessibilityComponent.js', [_modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/DOMElementProvider.js'], _modules['Accessibility/Utils/EventProvider.js'], _modules['Core/Globals.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Core/Utilities.js']], function (ChartUtilities, DOMElementProvider, EventProvider, H, HTMLUtilities, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Accessibility component class definition
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
- var doc = H.doc,
- win = H.win;
- var removeElement = HTMLUtilities.removeElement,
- getFakeMouseEvent = HTMLUtilities.getFakeMouseEvent;
- var extend = U.extend,
- fireEvent = U.fireEvent,
- merge = U.merge;
- /* eslint-disable valid-jsdoc */
- /** @lends Highcharts.AccessibilityComponent */
- var functionsToOverrideByDerivedClasses = {
- /**
- * Called on component initialization.
- */
- init: function () { },
- /**
- * Get keyboard navigation handler for this component.
- * @return {Highcharts.KeyboardNavigationHandler}
- */
- getKeyboardNavigation: function () { },
- /**
- * Called on updates to the chart,
- including options changes.
- * Note that this is also called on first render of chart.
- */
- onChartUpdate: function () { },
- /**
- * Called on every chart render.
- */
- onChartRender: function () { },
- /**
- * Called when accessibility is disabled or chart is destroyed.
- */
- destroy: function () { }
- };
- /**
- * The AccessibilityComponent base class, representing a part of the chart that
- * has accessibility logic connected to it. This class can be inherited from to
- * create a custom accessibility component for a chart.
- *
- * Components should take care to destroy added elements and unregister event
- * handlers on destroy. This is handled automatically if using this.addEvent and
- * this.createElement.
- *
- * @sample highcharts/accessibility/custom-component
- * Custom accessibility component
- *
- * @requires module:modules/accessibility
- * @class
- * @name Highcharts.AccessibilityComponent
- */
- function AccessibilityComponent() { }
- /**
- * @lends Highcharts.AccessibilityComponent
- */
- AccessibilityComponent.prototype = {
- /**
- * Initialize the class
- * @private
- * @param {Highcharts.Chart} chart
- * Chart object
- */
- initBase: function (chart) {
- this.chart = chart;
- this.eventProvider = new EventProvider();
- this.domElementProvider = new DOMElementProvider();
- // Key code enum for common keys
- this.keyCodes = {
- left: 37,
- right: 39,
- up: 38,
- down: 40,
- enter: 13,
- space: 32,
- esc: 27,
- tab: 9
- };
- },
- /**
- * Add an event to an element and keep track of it for later removal.
- * See EventProvider for details.
- * @private
- */
- addEvent: function () {
- return this.eventProvider.addEvent
- .apply(this.eventProvider, arguments);
- },
- /**
- * Create an element and keep track of it for later removal.
- * See DOMElementProvider for details.
- * @private
- */
- createElement: function () {
- return this.domElementProvider.createElement.apply(this.domElementProvider, arguments);
- },
- /**
- * Fire an event on an element that is either wrapped by Highcharts,
- * or a DOM element
- * @private
- * @param {Highcharts.HTMLElement|Highcharts.HTMLDOMElement|
- * Highcharts.SVGDOMElement|Highcharts.SVGElement} el
- * @param {Event} eventObject
- */
- fireEventOnWrappedOrUnwrappedElement: function (el, eventObject) {
- var type = eventObject.type;
- if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {
- if (el.dispatchEvent) {
- el.dispatchEvent(eventObject);
- }
- else {
- el.fireEvent(type, eventObject);
- }
- }
- else {
- fireEvent(el, type, eventObject);
- }
- },
- /**
- * Utility function to attempt to fake a click event on an element.
- * @private
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} element
- */
- fakeClickEvent: function (element) {
- if (element) {
- var fakeEventObject = getFakeMouseEvent('click');
- this.fireEventOnWrappedOrUnwrappedElement(element, fakeEventObject);
- }
- },
- /**
- * Add a new proxy group to the proxy container. Creates the proxy container
- * if it does not exist.
- * @private
- * @param {Highcharts.HTMLAttributes} [attrs]
- * The attributes to set on the new group div.
- * @return {Highcharts.HTMLDOMElement}
- * The new proxy group element.
- */
- addProxyGroup: function (attrs) {
- this.createOrUpdateProxyContainer();
- var groupDiv = this.createElement('div');
- Object.keys(attrs || {}).forEach(function (prop) {
- if (attrs[prop] !== null) {
- groupDiv.setAttribute(prop, attrs[prop]);
- }
- });
- this.chart.a11yProxyContainer.appendChild(groupDiv);
- return groupDiv;
- },
- /**
- * Creates and updates DOM position of proxy container
- * @private
- */
- createOrUpdateProxyContainer: function () {
- var chart = this.chart,
- rendererSVGEl = chart.renderer.box;
- chart.a11yProxyContainer = chart.a11yProxyContainer ||
- this.createProxyContainerElement();
- if (rendererSVGEl.nextSibling !== chart.a11yProxyContainer) {
- chart.container.insertBefore(chart.a11yProxyContainer, rendererSVGEl.nextSibling);
- }
- },
- /**
- * @private
- * @return {Highcharts.HTMLDOMElement} element
- */
- createProxyContainerElement: function () {
- var pc = doc.createElement('div');
- pc.className = 'highcharts-a11y-proxy-container';
- return pc;
- },
- /**
- * Create an invisible proxy HTML button in the same position as an SVG
- * element
- * @private
- * @param {Highcharts.SVGElement} svgElement
- * The wrapped svg el to proxy.
- * @param {Highcharts.HTMLDOMElement} parentGroup
- * The proxy group element in the proxy container to add this button to.
- * @param {Highcharts.SVGAttributes} [attributes]
- * Additional attributes to set.
- * @param {Highcharts.SVGElement} [posElement]
- * Element to use for positioning instead of svgElement.
- * @param {Function} [preClickEvent]
- * Function to call before click event fires.
- *
- * @return {Highcharts.HTMLDOMElement} The proxy button.
- */
- createProxyButton: function (svgElement, parentGroup, attributes, posElement, preClickEvent) {
- var svgEl = svgElement.element,
- proxy = this.createElement('button'),
- attrs = merge({
- 'aria-label': svgEl.getAttribute('aria-label')
- },
- attributes);
- Object.keys(attrs).forEach(function (prop) {
- if (attrs[prop] !== null) {
- proxy.setAttribute(prop, attrs[prop]);
- }
- });
- proxy.className = 'highcharts-a11y-proxy-button';
- if (svgElement.hasClass('highcharts-no-tooltip')) {
- proxy.className += ' highcharts-no-tooltip';
- }
- if (preClickEvent) {
- this.addEvent(proxy, 'click', preClickEvent);
- }
- this.setProxyButtonStyle(proxy);
- this.updateProxyButtonPosition(proxy, posElement || svgElement);
- this.proxyMouseEventsForButton(svgEl, proxy);
- // Add to chart div and unhide from screen readers
- parentGroup.appendChild(proxy);
- if (!attrs['aria-hidden']) {
- unhideChartElementFromAT(this.chart, proxy);
- }
- return proxy;
- },
- /**
- * Get the position relative to chart container for a wrapped SVG element.
- * @private
- * @param {Highcharts.SVGElement} element
- * The element to calculate position for.
- * @return {Highcharts.BBoxObject}
- * Object with x and y props for the position.
- */
- getElementPosition: function (element) {
- var el = element.element,
- div = this.chart.renderTo;
- if (div && el && el.getBoundingClientRect) {
- var rectEl = el.getBoundingClientRect(),
- rectDiv = div.getBoundingClientRect();
- return {
- x: rectEl.left - rectDiv.left,
- y: rectEl.top - rectDiv.top,
- width: rectEl.right - rectEl.left,
- height: rectEl.bottom - rectEl.top
- };
- }
- return { x: 0, y: 0, width: 1, height: 1 };
- },
- /**
- * @private
- * @param {Highcharts.HTMLElement} button The proxy element.
- */
- setProxyButtonStyle: function (button) {
- merge(true, button.style, {
- borderWidth: '0',
- backgroundColor: 'transparent',
- cursor: 'pointer',
- outline: 'none',
- opacity: '0.001',
- filter: 'alpha(opacity=1)',
- zIndex: '999',
- overflow: 'hidden',
- padding: '0',
- margin: '0',
- display: 'block',
- position: 'absolute'
- });
- button.style['-ms-filter'] =
- 'progid:DXImageTransform.Microsoft.Alpha(Opacity=1)';
- },
- /**
- * @private
- * @param {Highcharts.HTMLElement} proxy The proxy to update position of.
- * @param {Highcharts.SVGElement} posElement The element to overlay and take position from.
- */
- updateProxyButtonPosition: function (proxy, posElement) {
- var bBox = this.getElementPosition(posElement);
- merge(true, proxy.style, {
- width: (bBox.width || 1) + 'px',
- height: (bBox.height || 1) + 'px',
- left: (Math.round(bBox.x) || 0) + 'px',
- top: (Math.round(bBox.y) || 0) + 'px'
- });
- },
- /**
- * @private
- * @param {Highcharts.HTMLElement|Highcharts.HTMLDOMElement|
- * Highcharts.SVGDOMElement|Highcharts.SVGElement} source
- * @param {Highcharts.HTMLElement} button
- */
- proxyMouseEventsForButton: function (source, button) {
- var component = this;
- [
- 'click', 'touchstart', 'touchend', 'touchcancel', 'touchmove',
- 'mouseover', 'mouseenter', 'mouseleave', 'mouseout'
- ].forEach(function (evtType) {
- var isTouchEvent = evtType.indexOf('touch') === 0;
- component.addEvent(button, evtType, function (e) {
- var clonedEvent = isTouchEvent ?
- component.cloneTouchEvent(e) :
- component.cloneMouseEvent(e);
- if (source) {
- component.fireEventOnWrappedOrUnwrappedElement(source, clonedEvent);
- }
- e.stopPropagation();
- // #9682, #15318: Touch scrolling didnt work when touching a
- // component
- if (evtType !== 'touchstart' && evtType !== 'touchmove' && evtType !== 'touchend') {
- e.preventDefault();
- }
- }, { passive: false });
- });
- },
- /**
- * Utility function to clone a mouse event for re-dispatching.
- * @private
- * @param {global.MouseEvent} e The event to clone.
- * @return {global.MouseEvent} The cloned event
- */
- cloneMouseEvent: function (e) {
- if (typeof win.MouseEvent === 'function') {
- return new win.MouseEvent(e.type, e);
- }
- // No MouseEvent support, try using initMouseEvent
- if (doc.createEvent) {
- var evt = doc.createEvent('MouseEvent');
- if (evt.initMouseEvent) {
- evt.initMouseEvent(e.type, e.bubbles, // #10561, #12161
- e.cancelable, e.view || win, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
- return evt;
- }
- }
- return getFakeMouseEvent(e.type);
- },
- /**
- * Utility function to clone a touch event for re-dispatching.
- * @private
- * @param {global.TouchEvent} e The event to clone.
- * @return {global.TouchEvent} The cloned event
- */
- cloneTouchEvent: function (e) {
- var touchListToTouchArray = function (l) {
- var touchArray = [];
- for (var i = 0; i < l.length; ++i) {
- var item = l.item(i);
- if (item) {
- touchArray.push(item);
- }
- }
- return touchArray;
- };
- if (typeof win.TouchEvent === 'function') {
- var newEvent = new win.TouchEvent(e.type, {
- touches: touchListToTouchArray(e.touches),
- targetTouches: touchListToTouchArray(e.targetTouches),
- changedTouches: touchListToTouchArray(e.changedTouches),
- ctrlKey: e.ctrlKey,
- shiftKey: e.shiftKey,
- altKey: e.altKey,
- metaKey: e.metaKey,
- bubbles: e.bubbles,
- cancelable: e.cancelable,
- composed: e.composed,
- detail: e.detail,
- view: e.view
- });
- if (e.defaultPrevented) {
- newEvent.preventDefault();
- }
- return newEvent;
- }
- // Fallback to mouse event
- var fakeEvt = this.cloneMouseEvent(e);
- fakeEvt.touches = e.touches;
- fakeEvt.changedTouches = e.changedTouches;
- fakeEvt.targetTouches = e.targetTouches;
- return fakeEvt;
- },
- /**
- * Remove traces of the component.
- * @private
- */
- destroyBase: function () {
- removeElement(this.chart.a11yProxyContainer);
- this.domElementProvider.destroyCreatedElements();
- this.eventProvider.removeAddedEvents();
- }
- };
- extend(AccessibilityComponent.prototype, functionsToOverrideByDerivedClasses);
- return AccessibilityComponent;
- });
- _registerModule(_modules, 'Accessibility/KeyboardNavigation.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Accessibility/Utils/EventProvider.js']], function (Chart, H, U, HTMLUtilities, EventProvider) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Main keyboard navigation handling.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var doc = H.doc,
- win = H.win;
- var addEvent = U.addEvent,
- fireEvent = U.fireEvent;
- var getElement = HTMLUtilities.getElement;
- /* eslint-disable valid-jsdoc */
- // Add event listener to document to detect ESC key press and dismiss
- // hover/popup content.
- addEvent(doc, 'keydown', function (e) {
- var keycode = e.which || e.keyCode;
- var esc = 27;
- if (keycode === esc && H.charts) {
- H.charts.forEach(function (chart) {
- if (chart && chart.dismissPopupContent) {
- chart.dismissPopupContent();
- }
- });
- }
- });
- /**
- * Dismiss popup content in chart, including export menu and tooltip.
- */
- Chart.prototype.dismissPopupContent = function () {
- var chart = this;
- fireEvent(this, 'dismissPopupContent', {}, function () {
- if (chart.tooltip) {
- chart.tooltip.hide(0);
- }
- chart.hideExportMenu();
- });
- };
- /**
- * The KeyboardNavigation class, containing the overall keyboard navigation
- * logic for the chart.
- *
- * @requires module:modules/accessibility
- *
- * @private
- * @class
- * @param {Highcharts.Chart} chart
- * Chart object
- * @param {object} components
- * Map of component names to AccessibilityComponent objects.
- * @name Highcharts.KeyboardNavigation
- */
- function KeyboardNavigation(chart, components) {
- this.init(chart, components);
- }
- KeyboardNavigation.prototype = {
- /**
- * Initialize the class
- * @private
- * @param {Highcharts.Chart} chart
- * Chart object
- * @param {object} components
- * Map of component names to AccessibilityComponent objects.
- */
- init: function (chart, components) {
- var _this = this;
- var ep = this.eventProvider = new EventProvider();
- this.chart = chart;
- this.components = components;
- this.modules = [];
- this.currentModuleIx = 0;
- // Run an update to get all modules
- this.update();
- ep.addEvent(this.tabindexContainer, 'keydown', function (e) { return _this.onKeydown(e); });
- ep.addEvent(this.tabindexContainer, 'focus', function (e) { return _this.onFocus(e); });
- ['mouseup', 'touchend'].forEach(function (eventName) {
- return ep.addEvent(doc, eventName, function () { return _this.onMouseUp(); });
- });
- ['mousedown', 'touchstart'].forEach(function (eventName) {
- return ep.addEvent(chart.renderTo, eventName, function () {
- _this.isClickingChart = true;
- });
- });
- ep.addEvent(chart.renderTo, 'mouseover', function () {
- _this.pointerIsOverChart = true;
- });
- ep.addEvent(chart.renderTo, 'mouseout', function () {
- _this.pointerIsOverChart = false;
- });
- // Init first module
- if (this.modules.length) {
- this.modules[0].init(1);
- }
- },
- /**
- * Update the modules for the keyboard navigation.
- * @param {Array<string>} [order]
- * Array specifying the tab order of the components.
- */
- update: function (order) {
- var a11yOptions = this.chart.options.accessibility,
- keyboardOptions = a11yOptions && a11yOptions.keyboardNavigation,
- components = this.components;
- this.updateContainerTabindex();
- if (keyboardOptions &&
- keyboardOptions.enabled &&
- order &&
- order.length) {
- // We (still) have keyboard navigation. Update module list
- this.modules = order.reduce(function (modules, componentName) {
- var navModules = components[componentName].getKeyboardNavigation();
- return modules.concat(navModules);
- }, []);
- this.updateExitAnchor();
- }
- else {
- this.modules = [];
- this.currentModuleIx = 0;
- this.removeExitAnchor();
- }
- },
- /**
- * Function to run on container focus
- * @private
- * @param {global.FocusEvent} e Browser focus event.
- */
- onFocus: function (e) {
- var chart = this.chart;
- var focusComesFromChart = (e.relatedTarget &&
- chart.container.contains(e.relatedTarget));
- // Init keyboard nav if tabbing into chart
- if (!this.exiting &&
- !this.tabbingInBackwards &&
- !this.isClickingChart &&
- !focusComesFromChart &&
- this.modules[0]) {
- this.modules[0].init(1);
- }
- this.exiting = false;
- },
- /**
- * Reset chart navigation state if we click outside the chart and it's
- * not already reset.
- * @private
- */
- onMouseUp: function () {
- delete this.isClickingChart;
- if (!this.keyboardReset && !this.pointerIsOverChart) {
- var chart = this.chart,
- curMod = this.modules &&
- this.modules[this.currentModuleIx || 0];
- if (curMod && curMod.terminate) {
- curMod.terminate();
- }
- if (chart.focusElement) {
- chart.focusElement.removeFocusBorder();
- }
- this.currentModuleIx = 0;
- this.keyboardReset = true;
- }
- },
- /**
- * Function to run on keydown
- * @private
- * @param {global.KeyboardEvent} ev Browser keydown event.
- */
- onKeydown: function (ev) {
- var e = ev || win.event,
- preventDefault,
- curNavModule = this.modules && this.modules.length &&
- this.modules[this.currentModuleIx];
- // Used for resetting nav state when clicking outside chart
- this.keyboardReset = false;
- // Used for sending focus out of the chart by the modules.
- this.exiting = 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) {
- var response = curNavModule.run(e);
- if (response === curNavModule.response.success) {
- preventDefault = true;
- }
- else if (response === curNavModule.response.prev) {
- preventDefault = this.prev();
- }
- else if (response === curNavModule.response.next) {
- preventDefault = this.next();
- }
- if (preventDefault) {
- e.preventDefault();
- e.stopPropagation();
- }
- }
- },
- /**
- * Go to previous module.
- * @private
- */
- prev: function () {
- return this.move(-1);
- },
- /**
- * Go to next module.
- * @private
- */
- next: function () {
- return this.move(1);
- },
- /**
- * Move to prev/next module.
- * @private
- * @param {number} direction
- * Direction to move. +1 for next, -1 for prev.
- * @return {boolean}
- * True if there was a valid module in direction.
- */
- move: function (direction) {
- var curModule = this.modules && this.modules[this.currentModuleIx];
- if (curModule && curModule.terminate) {
- curModule.terminate(direction);
- }
- // Remove existing focus border if any
- if (this.chart.focusElement) {
- this.chart.focusElement.removeFocusBorder();
- }
- this.currentModuleIx += direction;
- var newModule = this.modules && this.modules[this.currentModuleIx];
- 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
- this.currentModuleIx = 0; // Reset counter
- // Set focus to chart or exit anchor depending on direction
- this.exiting = true;
- if (direction > 0) {
- this.exitAnchor.focus();
- }
- else {
- this.tabindexContainer.focus();
- }
- return false;
- },
- /**
- * We use an exit anchor 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
- */
- updateExitAnchor: function () {
- var endMarkerId = 'highcharts-end-of-chart-marker-' + this.chart.index,
- endMarker = getElement(endMarkerId);
- this.removeExitAnchor();
- if (endMarker) {
- this.makeElementAnExitAnchor(endMarker);
- this.exitAnchor = endMarker;
- }
- else {
- this.createExitAnchor();
- }
- },
- /**
- * Chart container should have tabindex if navigation is enabled.
- * @private
- */
- updateContainerTabindex: function () {
- var a11yOptions = this.chart.options.accessibility,
- keyboardOptions = a11yOptions && a11yOptions.keyboardNavigation,
- shouldHaveTabindex = !(keyboardOptions && keyboardOptions.enabled === false),
- chart = this.chart,
- container = chart.container;
- var tabindexContainer;
- if (chart.renderTo.hasAttribute('tabindex')) {
- container.removeAttribute('tabindex');
- tabindexContainer = chart.renderTo;
- }
- else {
- tabindexContainer = container;
- }
- this.tabindexContainer = tabindexContainer;
- var curTabindex = tabindexContainer.getAttribute('tabindex');
- if (shouldHaveTabindex && !curTabindex) {
- tabindexContainer.setAttribute('tabindex', '0');
- }
- else if (!shouldHaveTabindex) {
- chart.container.removeAttribute('tabindex');
- }
- },
- /**
- * @private
- */
- makeElementAnExitAnchor: function (el) {
- var chartTabindex = this.tabindexContainer.getAttribute('tabindex') || 0;
- el.setAttribute('class', 'highcharts-exit-anchor');
- el.setAttribute('tabindex', chartTabindex);
- el.setAttribute('aria-hidden', false);
- // Handle focus
- this.addExitAnchorEventsToEl(el);
- },
- /**
- * Add new exit anchor to the chart.
- *
- * @private
- */
- createExitAnchor: function () {
- var chart = this.chart,
- exitAnchor = this.exitAnchor = doc.createElement('div');
- chart.renderTo.appendChild(exitAnchor);
- this.makeElementAnExitAnchor(exitAnchor);
- },
- /**
- * @private
- */
- removeExitAnchor: function () {
- if (this.exitAnchor && this.exitAnchor.parentNode) {
- this.exitAnchor.parentNode
- .removeChild(this.exitAnchor);
- delete this.exitAnchor;
- }
- },
- /**
- * @private
- */
- addExitAnchorEventsToEl: function (element) {
- var chart = this.chart,
- keyboardNavigation = this;
- this.eventProvider.addEvent(element, 'focus', function (ev) {
- var e = ev || win.event,
- curModule,
- focusComesFromChart = (e.relatedTarget &&
- chart.container.contains(e.relatedTarget)),
- comingInBackwards = !(focusComesFromChart || keyboardNavigation.exiting);
- if (comingInBackwards) {
- // Focus the container instead
- keyboardNavigation.tabbingInBackwards = true;
- keyboardNavigation.tabindexContainer.focus();
- delete keyboardNavigation.tabbingInBackwards;
- e.preventDefault();
- // Move to last valid keyboard nav module
- // Note the we don't run it, just set the index
- if (keyboardNavigation.modules &&
- keyboardNavigation.modules.length) {
- keyboardNavigation.currentModuleIx =
- keyboardNavigation.modules.length - 1;
- curModule = keyboardNavigation.modules[keyboardNavigation.currentModuleIx];
- // Validate the module
- if (curModule &&
- curModule.validate && !curModule.validate()) {
- // Invalid. Try moving backwards to find next valid.
- keyboardNavigation.prev();
- }
- else if (curModule) {
- // We have a valid module, init it
- curModule.init(-1);
- }
- }
- }
- else {
- // Don't skip the next focus, we only skip once.
- keyboardNavigation.exiting = false;
- }
- });
- },
- /**
- * Remove all traces of keyboard navigation.
- * @private
- */
- destroy: function () {
- this.removeExitAnchor();
- this.eventProvider.removeAddedEvents();
- this.chart.container.removeAttribute('tabindex');
- }
- };
- return KeyboardNavigation;
- });
- _registerModule(_modules, 'Accessibility/Components/LegendComponent.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/Legend.js'], _modules['Core/Utilities.js'], _modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (Chart, H, Legend, U, AccessibilityComponent, KeyboardNavigationHandler, HTMLUtilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Accessibility component for chart legend.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var addEvent = U.addEvent,
- extend = U.extend,
- find = U.find,
- fireEvent = U.fireEvent,
- isNumber = U.isNumber;
- var removeElement = HTMLUtilities.removeElement,
- stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * @private
- */
- function scrollLegendToItem(legend, itemIx) {
- var itemPage = legend.allItems[itemIx].pageIx,
- curPage = legend.currentPage;
- if (typeof itemPage !== 'undefined' && itemPage + 1 !== curPage) {
- legend.scroll(1 + itemPage - curPage);
- }
- }
- /**
- * @private
- */
- function shouldDoLegendA11y(chart) {
- var items = chart.legend && chart.legend.allItems,
- legendA11yOptions = (chart.options.legend.accessibility || {});
- return !!(items && items.length &&
- !(chart.colorAxis && chart.colorAxis.length) &&
- legendA11yOptions.enabled !== false);
- }
- /**
- * Highlight legend item by index.
- *
- * @private
- * @function Highcharts.Chart#highlightLegendItem
- *
- * @param {number} ix
- *
- * @return {boolean}
- */
- Chart.prototype.highlightLegendItem = function (ix) {
- var items = this.legend.allItems,
- oldIx = this.accessibility &&
- this.accessibility.components.legend.highlightedLegendItemIx;
- if (items[ix]) {
- if (isNumber(oldIx) && items[oldIx]) {
- fireEvent(items[oldIx].legendGroup.element, 'mouseout');
- }
- scrollLegendToItem(this.legend, ix);
- this.setFocusToElement(items[ix].legendItem, items[ix].a11yProxyElement);
- fireEvent(items[ix].legendGroup.element, 'mouseover');
- return true;
- }
- return false;
- };
- // Keep track of pressed state for legend items
- addEvent(Legend, 'afterColorizeItem', function (e) {
- var chart = this.chart,
- a11yOptions = chart.options.accessibility,
- legendItem = e.item;
- if (a11yOptions.enabled && legendItem && legendItem.a11yProxyElement) {
- legendItem.a11yProxyElement.setAttribute('aria-pressed', e.visible ? 'true' : 'false');
- }
- });
- /**
- * The LegendComponent class
- *
- * @private
- * @class
- * @name Highcharts.LegendComponent
- */
- var LegendComponent = function () { };
- LegendComponent.prototype = new AccessibilityComponent();
- extend(LegendComponent.prototype, /** @lends Highcharts.LegendComponent */ {
- /**
- * Init the component
- * @private
- */
- init: function () {
- var component = this;
- this.proxyElementsList = [];
- this.recreateProxies();
- // Note: Chart could create legend dynamically, so events can not be
- // tied to the component's chart's current legend.
- this.addEvent(Legend, 'afterScroll', function () {
- if (this.chart === component.chart) {
- component.updateProxiesPositions();
- component.updateLegendItemProxyVisibility();
- this.chart.highlightLegendItem(component.highlightedLegendItemIx);
- }
- });
- this.addEvent(Legend, 'afterPositionItem', function (e) {
- if (this.chart === component.chart && this.chart.renderer) {
- component.updateProxyPositionForItem(e.item);
- }
- });
- },
- /**
- * @private
- */
- updateLegendItemProxyVisibility: function () {
- var legend = this.chart.legend,
- items = legend.allItems || [],
- curPage = legend.currentPage || 1,
- clipHeight = legend.clipHeight || 0;
- items.forEach(function (item) {
- var itemPage = item.pageIx || 0,
- y = item._legendItemPos ? item._legendItemPos[1] : 0,
- h = item.legendItem ? Math.round(item.legendItem.getBBox().height) : 0,
- hide = y + h - legend.pages[itemPage] > clipHeight || itemPage !== curPage - 1;
- if (item.a11yProxyElement) {
- item.a11yProxyElement.style.visibility = hide ?
- 'hidden' : 'visible';
- }
- });
- },
- /**
- * The legend needs updates on every render, in order to update positioning
- * of the proxy overlays.
- */
- onChartRender: function () {
- if (shouldDoLegendA11y(this.chart)) {
- this.updateProxiesPositions();
- }
- else {
- this.removeProxies();
- }
- },
- /**
- * @private
- */
- onChartUpdate: function () {
- this.updateLegendTitle();
- },
- /**
- * @private
- */
- updateProxiesPositions: function () {
- for (var _i = 0, _a = this.proxyElementsList; _i < _a.length; _i++) {
- var _b = _a[_i],
- element = _b.element,
- posElement = _b.posElement;
- this.updateProxyButtonPosition(element, posElement);
- }
- },
- /**
- * @private
- */
- updateProxyPositionForItem: function (item) {
- var proxyRef = find(this.proxyElementsList,
- function (ref) { return ref.item === item; });
- if (proxyRef) {
- this.updateProxyButtonPosition(proxyRef.element, proxyRef.posElement);
- }
- },
- /**
- * @private
- */
- recreateProxies: function () {
- this.removeProxies();
- if (shouldDoLegendA11y(this.chart)) {
- this.addLegendProxyGroup();
- this.addLegendListContainer();
- this.proxyLegendItems();
- this.updateLegendItemProxyVisibility();
- }
- },
- /**
- * @private
- */
- removeProxies: function () {
- removeElement(this.legendProxyGroup);
- this.proxyElementsList = [];
- },
- /**
- * @private
- */
- updateLegendTitle: function () {
- var chart = this.chart;
- var legendTitle = stripHTMLTags((chart.legend &&
- chart.legend.options.title &&
- chart.legend.options.title.text ||
- '').replace(/<br ?\/?>/g, ' '));
- var legendLabel = chart.langFormat('accessibility.legend.legendLabel' + (legendTitle ? '' : 'NoTitle'), {
- chart: chart,
- legendTitle: legendTitle
- });
- if (this.legendProxyGroup) {
- this.legendProxyGroup.setAttribute('aria-label', legendLabel);
- }
- },
- /**
- * @private
- */
- addLegendProxyGroup: function () {
- var a11yOptions = this.chart.options.accessibility,
- groupRole = a11yOptions.landmarkVerbosity === 'all' ?
- 'region' : null;
- this.legendProxyGroup = this.addProxyGroup({
- 'aria-label': '_placeholder_',
- role: groupRole
- });
- },
- /**
- * @private
- */
- addLegendListContainer: function () {
- if (this.legendProxyGroup) {
- var container = this.legendListContainer = this.createElement('ul');
- container.style.listStyle = 'none';
- this.legendProxyGroup.appendChild(container);
- }
- },
- /**
- * @private
- */
- proxyLegendItems: function () {
- var component = this,
- items = (this.chart.legend &&
- this.chart.legend.allItems || []);
- items.forEach(function (item) {
- if (item.legendItem && item.legendItem.element) {
- component.proxyLegendItem(item);
- }
- });
- },
- /**
- * @private
- * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item
- */
- proxyLegendItem: function (item) {
- if (!item.legendItem || !item.legendGroup || !this.legendListContainer) {
- return;
- }
- var itemLabel = this.chart.langFormat('accessibility.legend.legendItem', {
- chart: this.chart,
- itemName: stripHTMLTags(item.name),
- item: item
- }),
- attribs = {
- tabindex: -1,
- 'aria-pressed': item.visible,
- 'aria-label': itemLabel
- },
- // Considers useHTML
- proxyPositioningElement = item.legendGroup.div ?
- item.legendItem : item.legendGroup;
- var listItem = this.createElement('li');
- this.legendListContainer.appendChild(listItem);
- item.a11yProxyElement = this.createProxyButton(item.legendItem, listItem, attribs, proxyPositioningElement);
- this.proxyElementsList.push({
- item: item,
- element: item.a11yProxyElement,
- posElement: proxyPositioningElement
- });
- },
- /**
- * Get keyboard navigation handler for this component.
- * @return {Highcharts.KeyboardNavigationHandler}
- */
- getKeyboardNavigation: function () {
- var keys = this.keyCodes,
- component = this,
- chart = this.chart;
- return new KeyboardNavigationHandler(chart, {
- keyCodeMap: [
- [
- [keys.left, keys.right, keys.up, keys.down],
- function (keyCode) {
- return component.onKbdArrowKey(this, keyCode);
- }
- ],
- [
- [keys.enter, keys.space],
- function (keyCode) {
- if (H.isFirefox && keyCode === keys.space) { // #15520
- return this.response.success;
- }
- return component.onKbdClick(this);
- }
- ]
- ],
- validate: function () {
- return component.shouldHaveLegendNavigation();
- },
- init: function (direction) {
- return component.onKbdNavigationInit(direction);
- },
- terminate: function () {
- chart.legend.allItems.forEach(function (item) { return item.setState('', true); });
- }
- });
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @param {number} keyCode
- * @return {number}
- * Response code
- */
- onKbdArrowKey: function (keyboardNavigationHandler, keyCode) {
- var keys = this.keyCodes,
- response = keyboardNavigationHandler.response,
- chart = this.chart,
- a11yOptions = chart.options.accessibility,
- numItems = chart.legend.allItems.length,
- direction = (keyCode === keys.left || keyCode === keys.up) ? -1 : 1;
- var res = chart.highlightLegendItem(this.highlightedLegendItemIx + direction);
- if (res) {
- this.highlightedLegendItemIx += direction;
- return response.success;
- }
- if (numItems > 1 &&
- a11yOptions.keyboardNavigation.wrapAround) {
- keyboardNavigationHandler.init(direction);
- return response.success;
- }
- // No wrap, move
- return response[direction > 0 ? 'next' : 'prev'];
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @return {number}
- * Response code
- */
- onKbdClick: function (keyboardNavigationHandler) {
- var legendItem = this.chart.legend.allItems[this.highlightedLegendItemIx];
- if (legendItem && legendItem.a11yProxyElement) {
- fireEvent(legendItem.a11yProxyElement, 'click');
- }
- return keyboardNavigationHandler.response.success;
- },
- /**
- * @private
- * @return {boolean|undefined}
- */
- shouldHaveLegendNavigation: function () {
- var chart = this.chart,
- legendOptions = chart.options.legend || {},
- hasLegend = chart.legend && chart.legend.allItems,
- hasColorAxis = chart.colorAxis && chart.colorAxis.length,
- legendA11yOptions = (legendOptions.accessibility || {});
- return !!(hasLegend &&
- chart.legend.display &&
- !hasColorAxis &&
- legendA11yOptions.enabled &&
- legendA11yOptions.keyboardNavigation &&
- legendA11yOptions.keyboardNavigation.enabled);
- },
- /**
- * @private
- * @param {number} direction
- */
- onKbdNavigationInit: function (direction) {
- var chart = this.chart,
- lastIx = chart.legend.allItems.length - 1,
- ixToHighlight = direction > 0 ? 0 : lastIx;
- chart.highlightLegendItem(ixToHighlight);
- this.highlightedLegendItemIx = ixToHighlight;
- }
- });
- return LegendComponent;
- });
- _registerModule(_modules, 'Accessibility/Components/MenuComponent.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Utilities.js'], _modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (Chart, U, AccessibilityComponent, KeyboardNavigationHandler, ChartUtilities, HTMLUtilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Accessibility component for exporting menu.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var extend = U.extend;
- var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
- var removeElement = HTMLUtilities.removeElement,
- getFakeMouseEvent = HTMLUtilities.getFakeMouseEvent;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * Get the wrapped export button element of a chart.
- *
- * @private
- * @param {Highcharts.Chart} chart
- * @returns {Highcharts.SVGElement}
- */
- function getExportMenuButtonElement(chart) {
- return chart.exportSVGElements && chart.exportSVGElements[0];
- }
- /**
- * Show the export menu and focus the first item (if exists).
- *
- * @private
- * @function Highcharts.Chart#showExportMenu
- */
- Chart.prototype.showExportMenu = function () {
- var exportButton = getExportMenuButtonElement(this);
- if (exportButton) {
- var el = exportButton.element;
- if (el.onclick) {
- el.onclick(getFakeMouseEvent('click'));
- }
- }
- };
- /**
- * @private
- * @function Highcharts.Chart#hideExportMenu
- */
- 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(getFakeMouseEvent('mouseout'));
- }
- });
- chart.highlightedExportItemIx = 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 {boolean}
- */
- Chart.prototype.highlightExportItem = function (ix) {
- var listItem = this.exportDivElements && this.exportDivElements[ix],
- curHighlighted = this.exportDivElements &&
- this.exportDivElements[this.highlightedExportItemIx],
- hasSVGFocusSupport;
- if (listItem &&
- listItem.tagName === 'LI' &&
- !(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(getFakeMouseEvent('mouseout'));
- }
- if (listItem.onmouseover) {
- listItem.onmouseover(getFakeMouseEvent('mouseover'));
- }
- this.highlightedExportItemIx = ix;
- return true;
- }
- return false;
- };
- /**
- * Try to highlight the last valid export menu item.
- *
- * @private
- * @function Highcharts.Chart#highlightLastExportItem
- * @return {boolean}
- */
- Chart.prototype.highlightLastExportItem = function () {
- var chart = this,
- i;
- if (chart.exportDivElements) {
- i = chart.exportDivElements.length;
- while (i--) {
- if (chart.highlightExportItem(i)) {
- return true;
- }
- }
- }
- return false;
- };
- /**
- * @private
- * @param {Highcharts.Chart} chart
- */
- function exportingShouldHaveA11y(chart) {
- var exportingOpts = chart.options.exporting,
- exportButton = getExportMenuButtonElement(chart);
- return !!(exportingOpts &&
- exportingOpts.enabled !== false &&
- exportingOpts.accessibility &&
- exportingOpts.accessibility.enabled &&
- exportButton &&
- exportButton.element);
- }
- /**
- * The MenuComponent class
- *
- * @private
- * @class
- * @name Highcharts.MenuComponent
- */
- var MenuComponent = function () { };
- MenuComponent.prototype = new AccessibilityComponent();
- extend(MenuComponent.prototype, /** @lends Highcharts.MenuComponent */ {
- /**
- * Init the component
- */
- init: function () {
- var chart = this.chart,
- component = this;
- this.addEvent(chart, 'exportMenuShown', function () {
- component.onMenuShown();
- });
- this.addEvent(chart, 'exportMenuHidden', function () {
- component.onMenuHidden();
- });
- },
- /**
- * @private
- */
- onMenuHidden: function () {
- var menu = this.chart.exportContextMenu;
- if (menu) {
- menu.setAttribute('aria-hidden', 'true');
- }
- this.isExportMenuShown = false;
- this.setExportButtonExpandedState('false');
- },
- /**
- * @private
- */
- onMenuShown: function () {
- var chart = this.chart,
- menu = chart.exportContextMenu;
- if (menu) {
- this.addAccessibleContextMenuAttribs();
- unhideChartElementFromAT(chart, menu);
- }
- this.isExportMenuShown = true;
- this.setExportButtonExpandedState('true');
- },
- /**
- * @private
- * @param {string} stateStr
- */
- setExportButtonExpandedState: function (stateStr) {
- var button = this.exportButtonProxy;
- if (button) {
- button.setAttribute('aria-expanded', stateStr);
- }
- },
- /**
- * Called on each render of the chart. We need to update positioning of the
- * proxy overlay.
- */
- onChartRender: function () {
- var chart = this.chart,
- a11yOptions = chart.options.accessibility;
- // Always start with a clean slate
- removeElement(this.exportProxyGroup);
- // Set screen reader properties on export menu
- if (exportingShouldHaveA11y(chart)) {
- // Proxy button and group
- this.exportProxyGroup = this.addProxyGroup(
- // Wrap in a region div if verbosity is high
- a11yOptions.landmarkVerbosity === 'all' ? {
- 'aria-label': chart.langFormat('accessibility.exporting.exportRegionLabel', { chart: chart }),
- 'role': 'region'
- } : {});
- var button = getExportMenuButtonElement(this.chart);
- this.exportButtonProxy = this.createProxyButton(button, this.exportProxyGroup, {
- 'aria-label': chart.langFormat('accessibility.exporting.menuButtonLabel', { chart: chart }),
- 'aria-expanded': false
- });
- }
- },
- /**
- * @private
- */
- addAccessibleContextMenuAttribs: function () {
- var chart = this.chart,
- exportList = chart.exportDivElements;
- if (exportList && exportList.length) {
- // 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 === 'LI' &&
- !(item.children && item.children.length)) {
- item.setAttribute('tabindex', -1);
- }
- else {
- item.setAttribute('aria-hidden', 'true');
- }
- });
- // Set accessibility properties on parent div
- var parentDiv = exportList[0].parentNode;
- parentDiv.removeAttribute('aria-hidden');
- parentDiv.setAttribute('aria-label', chart.langFormat('accessibility.exporting.chartMenuLabel', { chart: chart }));
- }
- },
- /**
- * Get keyboard navigation handler for this component.
- * @return {Highcharts.KeyboardNavigationHandler}
- */
- getKeyboardNavigation: function () {
- var keys = this.keyCodes,
- chart = this.chart,
- component = this;
- return new KeyboardNavigationHandler(chart, {
- keyCodeMap: [
- // Arrow prev handler
- [
- [keys.left, keys.up],
- function () {
- return component.onKbdPrevious(this);
- }
- ],
- // Arrow next handler
- [
- [keys.right, keys.down],
- function () {
- return component.onKbdNext(this);
- }
- ],
- // Click handler
- [
- [keys.enter, keys.space],
- function () {
- return component.onKbdClick(this);
- }
- ]
- ],
- // Only run exporting navigation if exporting support exists and is
- // enabled on chart
- validate: function () {
- return chart.exportChart &&
- chart.options.exporting.enabled !== false &&
- chart.options.exporting.accessibility.enabled !==
- false;
- },
- // Focus export menu button
- init: function () {
- var exportBtn = component.exportButtonProxy,
- exportGroup = chart.exportingGroup;
- if (exportGroup && exportBtn) {
- chart.setFocusToElement(exportGroup, exportBtn);
- }
- },
- // Hide the menu
- terminate: function () {
- chart.hideExportMenu();
- }
- });
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @return {number}
- * Response code
- */
- onKbdPrevious: function (keyboardNavigationHandler) {
- var chart = this.chart,
- a11yOptions = chart.options.accessibility,
- response = keyboardNavigationHandler.response,
- i = chart.highlightedExportItemIx || 0;
- // Try to highlight prev item in list. Highlighting e.g.
- // separators will fail.
- while (i--) {
- if (chart.highlightExportItem(i)) {
- return response.success;
- }
- }
- // We failed, so wrap around or move to prev module
- if (a11yOptions.keyboardNavigation.wrapAround) {
- chart.highlightLastExportItem();
- return response.success;
- }
- return response.prev;
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @return {number}
- * Response code
- */
- onKbdNext: function (keyboardNavigationHandler) {
- var chart = this.chart,
- a11yOptions = chart.options.accessibility,
- response = keyboardNavigationHandler.response,
- i = (chart.highlightedExportItemIx || 0) + 1;
- // Try to highlight next item in list. Highlighting e.g.
- // separators will fail.
- for (; i < chart.exportDivElements.length; ++i) {
- if (chart.highlightExportItem(i)) {
- return response.success;
- }
- }
- // We failed, so wrap around or move to next module
- if (a11yOptions.keyboardNavigation.wrapAround) {
- chart.highlightExportItem(0);
- return response.success;
- }
- return response.next;
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @return {number}
- * Response code
- */
- onKbdClick: function (keyboardNavigationHandler) {
- var chart = this.chart,
- curHighlightedItem = chart.exportDivElements[chart.highlightedExportItemIx],
- exportButtonElement = getExportMenuButtonElement(chart).element;
- if (this.isExportMenuShown) {
- this.fakeClickEvent(curHighlightedItem);
- }
- else {
- this.fakeClickEvent(exportButtonElement);
- chart.highlightExportItem(0);
- }
- return keyboardNavigationHandler.response.success;
- }
- });
- return MenuComponent;
- });
- _registerModule(_modules, 'Accessibility/Components/SeriesComponent/SeriesKeyboardNavigation.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Series/Point.js'], _modules['Core/Series/Series.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Accessibility/Utils/EventProvider.js'], _modules['Accessibility/Utils/ChartUtilities.js']], function (Chart, Point, Series, SeriesRegistry, H, U, KeyboardNavigationHandler, EventProvider, ChartUtilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Handle keyboard navigation for series.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var seriesTypes = SeriesRegistry.seriesTypes;
- var doc = H.doc;
- var defined = U.defined,
- extend = U.extend,
- fireEvent = U.fireEvent;
- var getPointFromXY = ChartUtilities.getPointFromXY,
- getSeriesFromName = ChartUtilities.getSeriesFromName,
- scrollToPoint = ChartUtilities.scrollToPoint;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /*
- * 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.
- */
- Series.prototype.keyboardMoveVertical = true;
- ['column', 'pie'].forEach(function (type) {
- if (seriesTypes[type]) {
- seriesTypes[type].prototype.keyboardMoveVertical = false;
- }
- });
- /**
- * Get the index of a point in a series. This is needed when using e.g. data
- * grouping.
- *
- * @private
- * @function getPointIndex
- *
- * @param {Highcharts.AccessibilityPoint} point
- * The point to find index of.
- *
- * @return {number|undefined}
- * The index in the series.points array of the point.
- */
- function getPointIndex(point) {
- var index = point.index,
- points = point.series.points;
- var i = points.length;
- if (points[index] !== point) {
- while (i--) {
- if (points[i] === point) {
- return i;
- }
- }
- }
- else {
- return index;
- }
- }
- /**
- * Determine if series navigation should be skipped
- *
- * @private
- * @function isSkipSeries
- *
- * @param {Highcharts.Series} series
- *
- * @return {boolean|number|undefined}
- */
- function isSkipSeries(series) {
- var a11yOptions = series.chart.options.accessibility,
- seriesNavOptions = a11yOptions.keyboardNavigation.seriesNavigation,
- seriesA11yOptions = series.options.accessibility || {},
- seriesKbdNavOptions = seriesA11yOptions.keyboardNavigation;
- return seriesKbdNavOptions && seriesKbdNavOptions.enabled === false ||
- seriesA11yOptions.enabled === false ||
- series.options.enableMouseTracking === false || // #8440
- !series.visible ||
- // Skip all points in a series where pointNavigationEnabledThreshold is
- // reached
- (seriesNavOptions.pointNavigationEnabledThreshold &&
- seriesNavOptions.pointNavigationEnabledThreshold <=
- series.points.length);
- }
- /**
- * Determine if navigation for a point should be skipped
- *
- * @private
- * @function isSkipPoint
- *
- * @param {Highcharts.Point} point
- *
- * @return {boolean|number|undefined}
- */
- function isSkipPoint(point) {
- var a11yOptions = point.series.chart.options.accessibility;
- var pointA11yDisabled = (point.options.accessibility &&
- point.options.accessibility.enabled === false);
- return point.isNull &&
- a11yOptions.keyboardNavigation.seriesNavigation.skipNullPoints ||
- point.visible === false ||
- point.isInside === false ||
- pointA11yDisabled ||
- isSkipSeries(point.series);
- }
- /**
- * Get the point in a series that is closest (in pixel 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;
- var hasUndefinedPosition = function (point) {
- return !(defined(point.plotX) && defined(point.plotY));
- };
- if (hasUndefinedPosition(point)) {
- return;
- }
- while (i--) {
- dPoint = series.points[i];
- if (hasUndefinedPosition(dPoint)) {
- 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 defined(minIx) ? series.points[minIx] : void 0;
- }
- /**
- * Highlights a point (show tooltip and display hover state).
- *
- * @private
- * @function Highcharts.Point#highlight
- *
- * @return {Highcharts.Point}
- * This highlighted point.
- */
- 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
- }
- scrollToPoint(this);
- // 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|boolean}
- * Returns highlighted point on success, false on failure (no adjacent
- * point to highlight in chosen direction).
- */
- 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];
- var 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 {boolean|Highcharts.Point}
- */
- Series.prototype.highlightFirstValidPoint = function () {
- var curPoint = this.chart.highlightedPoint,
- start = (curPoint && curPoint.series) === this ?
- getPointIndex(curPoint) :
- 0,
- points = this.points,
- len = points.length;
- if (points && len) {
- for (var i = start; 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|boolean}
- */
- Chart.prototype.highlightAdjacentSeries = function (down) {
- var chart = this,
- curPoint = chart.highlightedPoint,
- lastSeries = chart.series && chart.series[chart.series.length - 1],
- lastPoint = lastSeries && lastSeries.points &&
- lastSeries.points[lastSeries.points.length - 1];
- var newSeries,
- newPoint,
- adjacentNewPoint;
- // 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|boolean}
- */
- Chart.prototype.highlightAdjacentPointVertical = function (down) {
- var curPoint = this.highlightedPoint;
- var minDistance = Infinity,
- bestPoint;
- if (!defined(curPoint.plotX) || !defined(curPoint.plotY)) {
- return false;
- }
- this.series.forEach(function (series) {
- if (isSkipSeries(series)) {
- return;
- }
- series.points.forEach(function (point) {
- if (!defined(point.plotY) || !defined(point.plotX) ||
- point === curPoint) {
- return;
- }
- var yDistance = point.plotY - curPoint.plotY;
- var 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 && series.yAxis.reversed) {
- yDistance *= -1;
- }
- if (yDistance <= 0 && down || yDistance >= 0 && !down || // Chk dir
- distance < 5 || // Points in same spot => infinite loop
- isSkipPoint(point)) {
- return;
- }
- if (distance < minDistance) {
- minDistance = distance;
- bestPoint = point;
- }
- });
- });
- return bestPoint ? bestPoint.highlight() : false;
- };
- /**
- * @private
- * @param {Highcharts.Chart} chart
- * @return {Highcharts.Point|boolean}
- */
- function highlightFirstValidPointInChart(chart) {
- var res = false;
- delete chart.highlightedPoint;
- res = chart.series.reduce(function (acc, cur) {
- return acc || cur.highlightFirstValidPoint();
- }, false);
- return res;
- }
- /**
- * @private
- * @param {Highcharts.Chart} chart
- * @return {Highcharts.Point|boolean}
- */
- function highlightLastValidPointInChart(chart) {
- var numSeries = chart.series.length;
- var i = numSeries,
- res = false;
- 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) {
- break;
- }
- }
- return res;
- }
- /**
- * @private
- * @param {Highcharts.Chart} chart
- */
- function updateChartFocusAfterDrilling(chart) {
- highlightFirstValidPointInChart(chart);
- if (chart.focusElement) {
- chart.focusElement.removeFocusBorder();
- }
- }
- /**
- * @private
- * @class
- * @name Highcharts.SeriesKeyboardNavigation
- */
- function SeriesKeyboardNavigation(chart, keyCodes) {
- this.keyCodes = keyCodes;
- this.chart = chart;
- }
- extend(SeriesKeyboardNavigation.prototype, /** @lends Highcharts.SeriesKeyboardNavigation */ {
- /**
- * Init the keyboard navigation
- */
- init: function () {
- var keyboardNavigation = this,
- chart = this.chart,
- e = this.eventProvider = new EventProvider();
- e.addEvent(Series, 'destroy', function () {
- return keyboardNavigation.onSeriesDestroy(this);
- });
- e.addEvent(chart, 'afterDrilldown', function () {
- updateChartFocusAfterDrilling(this);
- });
- e.addEvent(chart, 'drilldown', function (e) {
- var point = e.point,
- series = point.series;
- keyboardNavigation.lastDrilledDownPoint = {
- x: point.x,
- y: point.y,
- seriesName: series ? series.name : ''
- };
- });
- e.addEvent(chart, 'drillupall', function () {
- setTimeout(function () {
- keyboardNavigation.onDrillupAll();
- }, 10);
- });
- // Heatmaps et al. alter z-index in setState, causing elements
- // to lose focus
- e.addEvent(Point, 'afterSetState', function () {
- var point = this;
- var pointEl = point.graphic && point.graphic.element;
- if (chart.highlightedPoint === point &&
- doc.activeElement !== pointEl &&
- pointEl &&
- pointEl.focus) {
- pointEl.focus();
- }
- });
- },
- onDrillupAll: function () {
- // After drillup we want to find the point that was drilled down to and
- // highlight it.
- var last = this.lastDrilledDownPoint,
- chart = this.chart,
- series = last && getSeriesFromName(chart,
- last.seriesName);
- var point;
- if (last && series && defined(last.x) && defined(last.y)) {
- point = getPointFromXY(series, last.x, last.y);
- }
- // Container focus can be lost on drillup due to deleted elements.
- if (chart.container) {
- chart.container.focus();
- }
- if (point && point.highlight) {
- point.highlight();
- }
- if (chart.focusElement) {
- chart.focusElement.removeFocusBorder();
- }
- },
- /**
- * @return {Highcharts.KeyboardNavigationHandler}
- */
- getKeyboardNavigationHandler: function () {
- var keyboardNavigation = this,
- keys = this.keyCodes,
- chart = this.chart,
- inverted = chart.inverted;
- return new KeyboardNavigationHandler(chart, {
- keyCodeMap: [
- [inverted ? [keys.up, keys.down] : [keys.left, keys.right], function (keyCode) {
- return keyboardNavigation.onKbdSideways(this, keyCode);
- }],
- [inverted ? [keys.left, keys.right] : [keys.up, keys.down], function (keyCode) {
- return keyboardNavigation.onKbdVertical(this, keyCode);
- }],
- [[keys.enter, keys.space], function (keyCode, event) {
- var point = chart.highlightedPoint;
- if (point) {
- event.point = point;
- fireEvent(point.series, 'click', event);
- point.firePointEvent('click');
- }
- return this.response.success;
- }]
- ],
- init: function (dir) {
- return keyboardNavigation.onHandlerInit(this, dir);
- },
- terminate: function () {
- return keyboardNavigation.onHandlerTerminate();
- }
- });
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} handler
- * @param {number} keyCode
- * @return {number}
- * response
- */
- onKbdSideways: function (handler, keyCode) {
- var keys = this.keyCodes,
- isNext = keyCode === keys.right || keyCode === keys.down;
- return this.attemptHighlightAdjacentPoint(handler, isNext);
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} handler
- * @param {number} keyCode
- * @return {number}
- * response
- */
- onKbdVertical: function (handler, keyCode) {
- var chart = this.chart,
- keys = this.keyCodes,
- isNext = keyCode === keys.down || keyCode === keys.right,
- navOptions = chart.options.accessibility.keyboardNavigation
- .seriesNavigation;
- // Handle serialized mode, act like left/right
- if (navOptions.mode && navOptions.mode === 'serialize') {
- return this.attemptHighlightAdjacentPoint(handler, isNext);
- }
- // Normal mode, move between series
- var highlightMethod = (chart.highlightedPoint &&
- chart.highlightedPoint.series.keyboardMoveVertical) ?
- 'highlightAdjacentPointVertical' :
- 'highlightAdjacentSeries';
- chart[highlightMethod](isNext);
- return handler.response.success;
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} handler
- * @param {number} initDirection
- * @return {number}
- * response
- */
- onHandlerInit: function (handler, initDirection) {
- var chart = this.chart;
- if (initDirection > 0) {
- highlightFirstValidPointInChart(chart);
- }
- else {
- highlightLastValidPointInChart(chart);
- }
- return handler.response.success;
- },
- /**
- * @private
- */
- onHandlerTerminate: function () {
- var chart = this.chart;
- if (chart.tooltip) {
- chart.tooltip.hide(0);
- }
- var hoverSeries = chart.highlightedPoint && chart.highlightedPoint.series;
- if (hoverSeries && hoverSeries.onMouseOut) {
- hoverSeries.onMouseOut();
- }
- if (chart.highlightedPoint && chart.highlightedPoint.onMouseOut) {
- chart.highlightedPoint.onMouseOut();
- }
- delete chart.highlightedPoint;
- },
- /**
- * Function that attempts to highlight next/prev point. Handles wrap around.
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} handler
- * @param {boolean} directionIsNext
- * @return {number}
- * response
- */
- attemptHighlightAdjacentPoint: function (handler, directionIsNext) {
- var chart = this.chart,
- wrapAround = chart.options.accessibility.keyboardNavigation
- .wrapAround,
- highlightSuccessful = chart.highlightAdjacentPoint(directionIsNext);
- if (!highlightSuccessful) {
- if (wrapAround) {
- return handler.init(directionIsNext ? 1 : -1);
- }
- return handler.response[directionIsNext ? 'next' : 'prev'];
- }
- return handler.response.success;
- },
- /**
- * @private
- */
- onSeriesDestroy: function (series) {
- var chart = this.chart,
- currentHighlightedPointDestroyed = chart.highlightedPoint &&
- chart.highlightedPoint.series === series;
- if (currentHighlightedPointDestroyed) {
- delete chart.highlightedPoint;
- if (chart.focusElement) {
- chart.focusElement.removeFocusBorder();
- }
- }
- },
- /**
- * @private
- */
- destroy: function () {
- this.eventProvider.removeAddedEvents();
- }
- });
- return SeriesKeyboardNavigation;
- });
- _registerModule(_modules, 'Accessibility/Components/AnnotationsA11y.js', [_modules['Accessibility/Utils/HTMLUtilities.js']], function (HTMLUtilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Annotations accessibility code.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var escapeStringForHTML = HTMLUtilities.escapeStringForHTML,
- stripHTMLTagsFromString = HTMLUtilities.stripHTMLTagsFromString;
- /**
- * Get list of all annotation labels in the chart.
- *
- * @private
- * @param {Highcharts.Chart} chart The chart to get annotation info on.
- * @return {Array<object>} The labels, or empty array if none.
- */
- function getChartAnnotationLabels(chart) {
- var annotations = chart.annotations || [];
- return annotations.reduce(function (acc, cur) {
- if (cur.options &&
- cur.options.visible !== false) {
- acc = acc.concat(cur.labels);
- }
- return acc;
- }, []);
- }
- /**
- * Get the text of an annotation label.
- *
- * @private
- * @param {object} label The annotation label object
- * @return {string} The text in the label.
- */
- function getLabelText(label) {
- return ((label.options &&
- label.options.accessibility &&
- label.options.accessibility.description) ||
- (label.graphic &&
- label.graphic.text &&
- label.graphic.text.textStr) ||
- '');
- }
- /**
- * Describe an annotation label.
- *
- * @private
- * @param {object} label The annotation label object to describe
- * @return {string} The description for the label.
- */
- function getAnnotationLabelDescription(label) {
- var a11yDesc = (label.options &&
- label.options.accessibility &&
- label.options.accessibility.description);
- if (a11yDesc) {
- return a11yDesc;
- }
- var chart = label.chart;
- var labelText = getLabelText(label);
- var points = label.points;
- var getAriaLabel = function (point) { return (point.graphic &&
- point.graphic.element &&
- point.graphic.element.getAttribute('aria-label') ||
- ''); };
- var getValueDesc = function (point) {
- var valDesc = (point.accessibility &&
- point.accessibility.valueDescription ||
- getAriaLabel(point));
- var seriesName = (point &&
- point.series.name ||
- '');
- return (seriesName ? seriesName + ', ' : '') + 'data point ' + valDesc;
- };
- var pointValueDescriptions = points
- .filter(function (p) { return !!p.graphic; }) // Filter out mock points
- .map(getValueDesc)
- .filter(function (desc) { return !!desc; }); // Filter out points we can't describe
- var numPoints = pointValueDescriptions.length;
- var pointsSelector = numPoints > 1 ? 'MultiplePoints' : numPoints ? 'SinglePoint' : 'NoPoints';
- var langFormatStr = 'accessibility.screenReaderSection.annotations.description' + pointsSelector;
- var context = {
- annotationText: labelText,
- annotation: label,
- numPoints: numPoints,
- annotationPoint: pointValueDescriptions[0],
- additionalAnnotationPoints: pointValueDescriptions.slice(1)
- };
- return chart.langFormat(langFormatStr, context);
- }
- /**
- * Return array of HTML strings for each annotation label in the chart.
- *
- * @private
- * @param {Highcharts.Chart} chart The chart to get annotation info on.
- * @return {Array<string>} Array of strings with HTML content for each annotation label.
- */
- function getAnnotationListItems(chart) {
- var labels = getChartAnnotationLabels(chart);
- return labels.map(function (label) {
- var desc = escapeStringForHTML(stripHTMLTagsFromString(getAnnotationLabelDescription(label)));
- return desc ? "<li>" + desc + "</li>" : '';
- });
- }
- /**
- * Return the annotation info for a chart as string.
- *
- * @private
- * @param {Highcharts.Chart} chart The chart to get annotation info on.
- * @return {string} String with HTML content or empty string if no annotations.
- */
- function getAnnotationsInfoHTML(chart) {
- var annotations = chart.annotations;
- if (!(annotations && annotations.length)) {
- return '';
- }
- var annotationItems = getAnnotationListItems(chart);
- return "<ul style=\"list-style-type: none\">" + annotationItems.join(' ') + "</ul>";
- }
- /**
- * Return the texts for the annotation(s) connected to a point, or empty array
- * if none.
- *
- * @private
- * @param {Highcharts.Point} point The data point to get the annotation info from.
- * @return {Array<string>} Annotation texts
- */
- function getPointAnnotationTexts(point) {
- var labels = getChartAnnotationLabels(point.series.chart);
- var pointLabels = labels
- .filter(function (label) { return label.points.indexOf(point) > -1; });
- if (!pointLabels.length) {
- return [];
- }
- return pointLabels.map(function (label) { return "" + getLabelText(label); });
- }
- var AnnotationsA11y = {
- getAnnotationsInfoHTML: getAnnotationsInfoHTML,
- getAnnotationLabelDescription: getAnnotationLabelDescription,
- getAnnotationListItems: getAnnotationListItems,
- getPointAnnotationTexts: getPointAnnotationTexts
- };
- return AnnotationsA11y;
- });
- _registerModule(_modules, 'Accessibility/Components/SeriesComponent/SeriesDescriber.js', [_modules['Accessibility/Components/AnnotationsA11y.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Core/FormatUtilities.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Core/Tooltip.js'], _modules['Core/Utilities.js']], function (AnnotationsA11y, ChartUtilities, F, HTMLUtilities, Tooltip, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Place desriptions on a series and its points.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var getPointAnnotationTexts = AnnotationsA11y.getPointAnnotationTexts;
- var getAxisDescription = ChartUtilities.getAxisDescription,
- getSeriesFirstPointElement = ChartUtilities.getSeriesFirstPointElement,
- getSeriesA11yElement = ChartUtilities.getSeriesA11yElement,
- unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
- var format = F.format,
- numberFormat = F.numberFormat;
- var reverseChildNodes = HTMLUtilities.reverseChildNodes,
- stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString;
- var find = U.find,
- isNumber = U.isNumber,
- pick = U.pick,
- defined = U.defined;
- /* eslint-disable valid-jsdoc */
- /**
- * @private
- */
- function findFirstPointWithGraphic(point) {
- var sourcePointIndex = point.index;
- if (!point.series || !point.series.data || !defined(sourcePointIndex)) {
- return null;
- }
- return find(point.series.data, function (p) {
- return !!(p &&
- typeof p.index !== 'undefined' &&
- p.index > sourcePointIndex &&
- p.graphic &&
- p.graphic.element);
- }) || null;
- }
- /**
- * @private
- */
- function shouldAddDummyPoint(point) {
- // Note: Sunburst series use isNull for hidden points on drilldown.
- // Ignore these.
- var isSunburst = point.series && point.series.is('sunburst'),
- isNull = point.isNull;
- return isNull && !isSunburst;
- }
- /**
- * @private
- */
- function makeDummyElement(point, pos) {
- var renderer = point.series.chart.renderer,
- dummy = renderer.rect(pos.x,
- pos.y, 1, 1);
- dummy.attr({
- 'class': 'highcharts-a11y-dummy-point',
- fill: 'none',
- opacity: 0,
- 'fill-opacity': 0,
- 'stroke-opacity': 0
- });
- return dummy;
- }
- /**
- * @private
- * @param {Highcharts.Point} point
- * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|undefined}
- */
- function addDummyPointElement(point) {
- var series = point.series,
- firstPointWithGraphic = findFirstPointWithGraphic(point),
- firstGraphic = firstPointWithGraphic && firstPointWithGraphic.graphic,
- parentGroup = firstGraphic ?
- firstGraphic.parentGroup :
- series.graph || series.group,
- dummyPos = firstPointWithGraphic ? {
- x: pick(point.plotX,
- firstPointWithGraphic.plotX, 0),
- y: pick(point.plotY,
- firstPointWithGraphic.plotY, 0)
- } : {
- x: pick(point.plotX, 0),
- y: pick(point.plotY, 0)
- },
- dummyElement = makeDummyElement(point,
- dummyPos);
- if (parentGroup && parentGroup.element) {
- point.graphic = dummyElement;
- point.hasDummyGraphic = true;
- dummyElement.add(parentGroup);
- // Move to correct pos in DOM
- parentGroup.element.insertBefore(dummyElement.element, firstGraphic ? firstGraphic.element : null);
- return dummyElement.element;
- }
- }
- /**
- * @private
- * @param {Highcharts.Series} series
- * @return {boolean}
- */
- function hasMorePointsThanDescriptionThreshold(series) {
- var chartA11yOptions = series.chart.options.accessibility,
- threshold = (chartA11yOptions.series.pointDescriptionEnabledThreshold);
- return !!(threshold !== false &&
- series.points &&
- series.points.length >= threshold);
- }
- /**
- * @private
- * @param {Highcharts.Series} series
- * @return {boolean}
- */
- function shouldSetScreenReaderPropsOnPoints(series) {
- var seriesA11yOptions = series.options.accessibility || {};
- return !hasMorePointsThanDescriptionThreshold(series) &&
- !seriesA11yOptions.exposeAsGroupOnly;
- }
- /**
- * @private
- * @param {Highcharts.Series} series
- * @return {boolean}
- */
- function shouldSetKeyboardNavPropsOnPoints(series) {
- var chartA11yOptions = series.chart.options.accessibility,
- seriesNavOptions = chartA11yOptions.keyboardNavigation.seriesNavigation;
- return !!(series.points && (series.points.length <
- seriesNavOptions.pointNavigationEnabledThreshold ||
- seriesNavOptions.pointNavigationEnabledThreshold === false));
- }
- /**
- * @private
- * @param {Highcharts.Series} series
- * @return {boolean}
- */
- function shouldDescribeSeriesElement(series) {
- var chart = series.chart,
- chartOptions = chart.options.chart,
- chartHas3d = chartOptions.options3d && chartOptions.options3d.enabled,
- hasMultipleSeries = chart.series.length > 1,
- describeSingleSeriesOption = chart.options.accessibility.series.describeSingleSeries,
- exposeAsGroupOnlyOption = (series.options.accessibility || {}).exposeAsGroupOnly,
- noDescribe3D = chartHas3d && hasMultipleSeries;
- return !noDescribe3D && (hasMultipleSeries || describeSingleSeriesOption ||
- exposeAsGroupOnlyOption || hasMorePointsThanDescriptionThreshold(series));
- }
- /**
- * @private
- * @param {Highcharts.Point} point
- * @param {number} value
- * @return {string}
- */
- function pointNumberToString(point, value) {
- var chart = point.series.chart,
- a11yPointOptions = chart.options.accessibility.point || {},
- tooltipOptions = point.series.tooltipOptions || {},
- lang = chart.options.lang;
- if (isNumber(value)) {
- return numberFormat(value, a11yPointOptions.valueDecimals ||
- tooltipOptions.valueDecimals ||
- -1, lang.decimalPoint, lang.accessibility.thousandsSep || lang.thousandsSep);
- }
- return value;
- }
- /**
- * @private
- * @param {Highcharts.Series} series
- * @return {string}
- */
- function getSeriesDescriptionText(series) {
- var seriesA11yOptions = series.options.accessibility || {},
- descOpt = seriesA11yOptions.description;
- return descOpt && series.chart.langFormat('accessibility.series.description', {
- description: descOpt,
- series: series
- }) || '';
- }
- /**
- * @private
- * @param {Highcharts.series} series
- * @param {string} axisCollection
- * @return {string}
- */
- function getSeriesAxisDescriptionText(series, axisCollection) {
- var axis = series[axisCollection];
- return series.chart.langFormat('accessibility.series.' + axisCollection + 'Description', {
- name: getAxisDescription(axis),
- series: series
- });
- }
- /**
- * Get accessible time description for a point on a datetime axis.
- *
- * @private
- * @function Highcharts.Point#getTimeDescription
- * @param {Highcharts.Point} point
- * @return {string|undefined}
- * The description as string.
- */
- function getPointA11yTimeDescription(point) {
- var series = point.series,
- chart = series.chart,
- a11yOptions = chart.options.accessibility.point || {},
- hasDateXAxis = series.xAxis && series.xAxis.dateTime;
- if (hasDateXAxis) {
- var tooltipDateFormat = Tooltip.prototype.getXDateFormat.call({
- getDateFormat: Tooltip.prototype.getDateFormat,
- chart: chart
- },
- point,
- chart.options.tooltip,
- series.xAxis),
- dateFormat = a11yOptions.dateFormatter &&
- a11yOptions.dateFormatter(point) ||
- a11yOptions.dateFormat ||
- tooltipDateFormat;
- return chart.time.dateFormat(dateFormat, point.x, void 0);
- }
- }
- /**
- * @private
- * @param {Highcharts.Point} point
- * @return {string}
- */
- function getPointXDescription(point) {
- var timeDesc = getPointA11yTimeDescription(point), xAxis = point.series.xAxis || {}, pointCategory = xAxis.categories && defined(point.category) &&
- ('' + point.category).replace('<br/>', ' '), canUseId = point.id && point.id.indexOf('highcharts-') < 0, fallback = 'x, ' + point.x;
- return point.name || timeDesc || pointCategory ||
- (canUseId ? point.id : fallback);
- }
- /**
- * @private
- * @param {Highcharts.Point} point
- * @param {string} prefix
- * @param {string} suffix
- * @return {string}
- */
- function getPointArrayMapValueDescription(point, prefix, suffix) {
- var pre = prefix || '', suf = suffix || '', keyToValStr = function (key) {
- var num = pointNumberToString(point, pick(point[key], point.options[key]));
- return key + ': ' + pre + num + suf;
- }, pointArrayMap = point.series.pointArrayMap;
- return pointArrayMap.reduce(function (desc, key) {
- return desc + (desc.length ? ', ' : '') + keyToValStr(key);
- }, '');
- }
- /**
- * @private
- * @param {Highcharts.Point} point
- * @return {string}
- */
- function getPointValue(point) {
- var series = point.series,
- a11yPointOpts = series.chart.options.accessibility.point || {},
- tooltipOptions = series.tooltipOptions || {},
- valuePrefix = a11yPointOpts.valuePrefix ||
- tooltipOptions.valuePrefix || '',
- valueSuffix = a11yPointOpts.valueSuffix ||
- tooltipOptions.valueSuffix || '',
- fallbackKey = (typeof point.value !==
- 'undefined' ?
- 'value' : 'y'),
- fallbackDesc = pointNumberToString(point,
- point[fallbackKey]);
- if (point.isNull) {
- return series.chart.langFormat('accessibility.series.nullPointValue', {
- point: point
- });
- }
- if (series.pointArrayMap) {
- return getPointArrayMapValueDescription(point, valuePrefix, valueSuffix);
- }
- return valuePrefix + fallbackDesc + valueSuffix;
- }
- /**
- * Return the description for the annotation(s) connected to a point, or empty
- * string if none.
- *
- * @private
- * @param {Highcharts.Point} point The data point to get the annotation info from.
- * @return {string} Annotation description
- */
- function getPointAnnotationDescription(point) {
- var chart = point.series.chart;
- var langKey = 'accessibility.series.pointAnnotationsDescription';
- var annotations = getPointAnnotationTexts(point);
- var context = { point: point,
- annotations: annotations };
- return annotations.length ? chart.langFormat(langKey, context) : '';
- }
- /**
- * Return string with information about point.
- * @private
- * @return {string}
- */
- function getPointValueDescription(point) {
- var series = point.series, chart = series.chart, pointValueDescriptionFormat = chart.options.accessibility
- .point.valueDescriptionFormat, showXDescription = pick(series.xAxis &&
- series.xAxis.options.accessibility &&
- series.xAxis.options.accessibility.enabled, !chart.angular), xDesc = showXDescription ? getPointXDescription(point) : '', context = {
- point: point,
- index: defined(point.index) ? (point.index + 1) : '',
- xDescription: xDesc,
- value: getPointValue(point),
- separator: showXDescription ? ', ' : ''
- };
- return format(pointValueDescriptionFormat, context, chart);
- }
- /**
- * Return string with information about point.
- * @private
- * @return {string}
- */
- function defaultPointDescriptionFormatter(point) {
- var series = point.series, chart = series.chart, valText = getPointValueDescription(point), description = point.options && point.options.accessibility &&
- point.options.accessibility.description, userDescText = description ? ' ' + description : '', seriesNameText = chart.series.length > 1 && series.name ?
- ' ' + series.name + '.' : '', annotationsDesc = getPointAnnotationDescription(point), pointAnnotationsText = annotationsDesc ? ' ' + annotationsDesc : '';
- point.accessibility = point.accessibility || {};
- point.accessibility.valueDescription = valText;
- return valText + userDescText + seriesNameText + pointAnnotationsText;
- }
- /**
- * Set a11y props on a point element
- * @private
- * @param {Highcharts.Point} point
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} pointElement
- */
- function setPointScreenReaderAttribs(point, pointElement) {
- var series = point.series,
- a11yPointOptions = series.chart.options.accessibility.point || {},
- seriesA11yOptions = series.options.accessibility || {},
- label = stripHTMLTags(seriesA11yOptions.pointDescriptionFormatter &&
- seriesA11yOptions.pointDescriptionFormatter(point) ||
- a11yPointOptions.descriptionFormatter &&
- a11yPointOptions.descriptionFormatter(point) ||
- defaultPointDescriptionFormatter(point));
- pointElement.setAttribute('role', 'img');
- pointElement.setAttribute('aria-label', label);
- }
- /**
- * Add accessible info to individual point elements of a series
- * @private
- * @param {Highcharts.Series} series
- */
- function describePointsInSeries(series) {
- var setScreenReaderProps = shouldSetScreenReaderPropsOnPoints(series),
- setKeyboardProps = shouldSetKeyboardNavPropsOnPoints(series);
- if (setScreenReaderProps || setKeyboardProps) {
- series.points.forEach(function (point) {
- var pointEl = point.graphic && point.graphic.element ||
- shouldAddDummyPoint(point) && addDummyPointElement(point);
- var pointA11yDisabled = (point.options &&
- point.options.accessibility &&
- point.options.accessibility.enabled === false);
- if (pointEl) {
- // We always set tabindex, as long as we are setting props.
- // When setting tabindex, also remove default outline to
- // avoid ugly border on click.
- pointEl.setAttribute('tabindex', '-1');
- pointEl.style.outline = '0';
- if (setScreenReaderProps && !pointA11yDisabled) {
- setPointScreenReaderAttribs(point, pointEl);
- }
- else {
- pointEl.setAttribute('aria-hidden', true);
- }
- }
- });
- }
- }
- /**
- * Return string with information about series.
- * @private
- * @return {string}
- */
- function defaultSeriesDescriptionFormatter(series) {
- var chart = series.chart,
- chartTypes = chart.types || [],
- description = getSeriesDescriptionText(series),
- shouldDescribeAxis = function (coll) {
- return chart[coll] && chart[coll].length > 1 && series[coll];
- }, xAxisInfo = getSeriesAxisDescriptionText(series, 'xAxis'), yAxisInfo = getSeriesAxisDescriptionText(series, 'yAxis'), summaryContext = {
- name: series.name || '',
- ix: series.index + 1,
- numSeries: chart.series && chart.series.length,
- numPoints: series.points && series.points.length,
- series: series
- }, combinationSuffix = chartTypes.length > 1 ? 'Combination' : '', summary = chart.langFormat('accessibility.series.summary.' + series.type + combinationSuffix, summaryContext) || chart.langFormat('accessibility.series.summary.default' + combinationSuffix, summaryContext);
- return summary + (description ? ' ' + description : '') + (shouldDescribeAxis('yAxis') ? ' ' + yAxisInfo : '') + (shouldDescribeAxis('xAxis') ? ' ' + xAxisInfo : '');
- }
- /**
- * Set a11y props on a series element
- * @private
- * @param {Highcharts.Series} series
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} seriesElement
- */
- function describeSeriesElement(series, seriesElement) {
- var seriesA11yOptions = series.options.accessibility || {},
- a11yOptions = series.chart.options.accessibility,
- landmarkVerbosity = a11yOptions.landmarkVerbosity;
- // Handle role attribute
- if (seriesA11yOptions.exposeAsGroupOnly) {
- seriesElement.setAttribute('role', 'img');
- }
- else if (landmarkVerbosity === 'all') {
- seriesElement.setAttribute('role', 'region');
- } /* else do not add role */
- seriesElement.setAttribute('tabindex', '-1');
- seriesElement.style.outline = '0'; // Don't show browser outline on click, despite tabindex
- seriesElement.setAttribute('aria-label', stripHTMLTags(a11yOptions.series.descriptionFormatter &&
- a11yOptions.series.descriptionFormatter(series) ||
- defaultSeriesDescriptionFormatter(series)));
- }
- /**
- * Put accessible info on series and points of a series.
- * @param {Highcharts.Series} series The series to add info on.
- */
- function describeSeries(series) {
- var chart = series.chart,
- firstPointEl = getSeriesFirstPointElement(series),
- seriesEl = getSeriesA11yElement(series),
- is3d = chart.is3d && chart.is3d();
- 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.
- // Due to z-index issues we can not do this for 3D charts.
- if (seriesEl.lastChild === firstPointEl && !is3d) {
- reverseChildNodes(seriesEl);
- }
- describePointsInSeries(series);
- unhideChartElementFromAT(chart, seriesEl);
- if (shouldDescribeSeriesElement(series)) {
- describeSeriesElement(series, seriesEl);
- }
- else {
- seriesEl.setAttribute('aria-label', '');
- }
- }
- }
- var SeriesDescriber = {
- describeSeries: describeSeries,
- defaultPointDescriptionFormatter: defaultPointDescriptionFormatter,
- defaultSeriesDescriptionFormatter: defaultSeriesDescriptionFormatter,
- getPointA11yTimeDescription: getPointA11yTimeDescription,
- getPointXDescription: getPointXDescription,
- getPointValue: getPointValue,
- getPointValueDescription: getPointValueDescription
- };
- return SeriesDescriber;
- });
- _registerModule(_modules, 'Accessibility/Utils/Announcer.js', [_modules['Core/Globals.js'], _modules['Core/Renderer/HTML/AST.js'], _modules['Accessibility/Utils/DOMElementProvider.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (H, AST, DOMElementProvider, HTMLUtilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Create announcer to speak messages to screen readers and other AT.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var doc = H.doc;
- var setElAttrs = HTMLUtilities.setElAttrs,
- visuallyHideElement = HTMLUtilities.visuallyHideElement;
- var Announcer = /** @class */ (function () {
- function Announcer(chart, type) {
- this.chart = chart;
- this.domElementProvider = new DOMElementProvider();
- this.announceRegion = this.addAnnounceRegion(type);
- }
- Announcer.prototype.destroy = function () {
- this.domElementProvider.destroyCreatedElements();
- };
- Announcer.prototype.announce = function (message) {
- var _this = this;
- AST.setElementHTML(this.announceRegion, message);
- // Delete contents after a little while to avoid user finding the live
- // region in the DOM.
- if (this.clearAnnouncementRegionTimer) {
- clearTimeout(this.clearAnnouncementRegionTimer);
- }
- this.clearAnnouncementRegionTimer = setTimeout(function () {
- _this.announceRegion.innerHTML = '';
- delete _this.clearAnnouncementRegionTimer;
- }, 1000);
- };
- Announcer.prototype.addAnnounceRegion = function (type) {
- var chartContainer = this.chart.announcerContainer || this.createAnnouncerContainer();
- var div = this.domElementProvider.createElement('div');
- setElAttrs(div, {
- 'aria-hidden': false,
- 'aria-live': type
- });
- visuallyHideElement(div);
- chartContainer.appendChild(div);
- return div;
- };
- Announcer.prototype.createAnnouncerContainer = function () {
- var chart = this.chart;
- var container = doc.createElement('div');
- setElAttrs(container, {
- 'aria-hidden': false,
- style: 'position:relative',
- 'class': 'highcharts-announcer-container'
- });
- chart.renderTo.insertBefore(container, chart.renderTo.firstChild);
- chart.announcerContainer = container;
- return container;
- };
- return Announcer;
- }());
- H.Announcer = Announcer;
- return Announcer;
- });
- _registerModule(_modules, 'Accessibility/Components/SeriesComponent/NewDataAnnouncer.js', [_modules['Core/Globals.js'], _modules['Core/Series/Series.js'], _modules['Core/Utilities.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Components/SeriesComponent/SeriesDescriber.js'], _modules['Accessibility/Utils/Announcer.js'], _modules['Accessibility/Utils/EventProvider.js']], function (H, Series, U, ChartUtilities, SeriesDescriber, Announcer, EventProvider) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Handle announcing new data for a chart.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var extend = U.extend,
- defined = U.defined;
- var getChartTitle = ChartUtilities.getChartTitle;
- var defaultPointDescriptionFormatter = SeriesDescriber
- .defaultPointDescriptionFormatter,
- defaultSeriesDescriptionFormatter = SeriesDescriber
- .defaultSeriesDescriptionFormatter;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * @private
- */
- function chartHasAnnounceEnabled(chart) {
- return !!chart.options.accessibility.announceNewData.enabled;
- }
- /**
- * @private
- */
- function findPointInDataArray(point) {
- var candidates = point.series.data.filter(function (candidate) {
- return point.x === candidate.x && point.y === candidate.y;
- });
- return candidates.length === 1 ? candidates[0] : point;
- }
- /**
- * Get array of unique series from two arrays
- * @private
- */
- function getUniqueSeries(arrayA, arrayB) {
- var uniqueSeries = (arrayA || []).concat(arrayB || [])
- .reduce(function (acc,
- cur) {
- acc[cur.name + cur.index] = cur;
- return acc;
- }, {});
- return Object.keys(uniqueSeries).map(function (ix) {
- return uniqueSeries[ix];
- });
- }
- /**
- * @private
- * @class
- */
- var NewDataAnnouncer = function (chart) {
- this.chart = chart;
- };
- extend(NewDataAnnouncer.prototype, {
- /**
- * Initialize the new data announcer.
- * @private
- */
- init: function () {
- var chart = this.chart;
- var announceOptions = chart.options.accessibility.announceNewData;
- var announceType = announceOptions.interruptUser ? 'assertive' : 'polite';
- this.lastAnnouncementTime = 0;
- this.dirty = {
- allSeries: {}
- };
- this.eventProvider = new EventProvider();
- this.announcer = new Announcer(chart, announceType);
- this.addEventListeners();
- },
- /**
- * Remove traces of announcer.
- * @private
- */
- destroy: function () {
- this.eventProvider.removeAddedEvents();
- this.announcer.destroy();
- },
- /**
- * Add event listeners for the announcer
- * @private
- */
- addEventListeners: function () {
- var announcer = this,
- chart = this.chart,
- e = this.eventProvider;
- e.addEvent(chart, 'afterDrilldown', function () {
- announcer.lastAnnouncementTime = 0;
- });
- e.addEvent(Series, 'updatedData', function () {
- announcer.onSeriesUpdatedData(this);
- });
- e.addEvent(chart, 'afterAddSeries', function (e) {
- announcer.onSeriesAdded(e.series);
- });
- e.addEvent(Series, 'addPoint', function (e) {
- announcer.onPointAdded(e.point);
- });
- e.addEvent(chart, 'redraw', function () {
- announcer.announceDirtyData();
- });
- },
- /**
- * On new data in the series, make sure we add it to the dirty list.
- * @private
- * @param {Highcharts.Series} series
- */
- onSeriesUpdatedData: function (series) {
- var chart = this.chart;
- if (series.chart === chart && chartHasAnnounceEnabled(chart)) {
- this.dirty.hasDirty = true;
- this.dirty.allSeries[series.name + series.index] = series;
- }
- },
- /**
- * On new data series added, update dirty list.
- * @private
- * @param {Highcharts.Series} series
- */
- onSeriesAdded: function (series) {
- if (chartHasAnnounceEnabled(this.chart)) {
- this.dirty.hasDirty = true;
- this.dirty.allSeries[series.name + series.index] = series;
- // Add it to newSeries storage unless we already have one
- this.dirty.newSeries = defined(this.dirty.newSeries) ?
- void 0 : series;
- }
- },
- /**
- * On new point added, update dirty list.
- * @private
- * @param {Highcharts.Point} point
- */
- onPointAdded: function (point) {
- var chart = point.series.chart;
- if (this.chart === chart && chartHasAnnounceEnabled(chart)) {
- // Add it to newPoint storage unless we already have one
- this.dirty.newPoint = defined(this.dirty.newPoint) ?
- void 0 : point;
- }
- },
- /**
- * Gather what we know and announce the data to user.
- * @private
- */
- announceDirtyData: function () {
- var chart = this.chart,
- announcer = this;
- if (chart.options.accessibility.announceNewData &&
- this.dirty.hasDirty) {
- var newPoint = this.dirty.newPoint;
- // If we have a single new point, see if we can find it in the
- // data array. Otherwise we can only pass through options to
- // the description builder, and it is a bit sparse in info.
- if (newPoint) {
- newPoint = findPointInDataArray(newPoint);
- }
- this.queueAnnouncement(Object.keys(this.dirty.allSeries).map(function (ix) {
- return announcer.dirty.allSeries[ix];
- }), this.dirty.newSeries, newPoint);
- // Reset
- this.dirty = {
- allSeries: {}
- };
- }
- },
- /**
- * Announce to user that there is new data.
- * @private
- * @param {Array<Highcharts.Series>} dirtySeries
- * Array of series with new data.
- * @param {Highcharts.Series} [newSeries]
- * If a single new series was added, a reference to this series.
- * @param {Highcharts.Point} [newPoint]
- * If a single point was added, a reference to this point.
- */
- queueAnnouncement: function (dirtySeries, newSeries, newPoint) {
- var _this = this;
- var chart = this.chart;
- var annOptions = chart.options.accessibility.announceNewData;
- if (annOptions.enabled) {
- var now = +new Date();
- var dTime = now - this.lastAnnouncementTime;
- var time = Math.max(0,
- annOptions.minAnnounceInterval - dTime);
- // Add series from previously queued announcement.
- var allSeries = getUniqueSeries(this.queuedAnnouncement && this.queuedAnnouncement.series,
- dirtySeries);
- // Build message and announce
- var message = this.buildAnnouncementMessage(allSeries,
- newSeries,
- newPoint);
- if (message) {
- // Is there already one queued?
- if (this.queuedAnnouncement) {
- clearTimeout(this.queuedAnnouncementTimer);
- }
- // Build the announcement
- this.queuedAnnouncement = {
- time: now,
- message: message,
- series: allSeries
- };
- // Queue the announcement
- this.queuedAnnouncementTimer = setTimeout(function () {
- if (_this && _this.announcer) {
- _this.lastAnnouncementTime = +new Date();
- _this.announcer.announce(_this.queuedAnnouncement.message);
- delete _this.queuedAnnouncement;
- delete _this.queuedAnnouncementTimer;
- }
- }, time);
- }
- }
- },
- /**
- * Get announcement message for new data.
- * @private
- * @param {Array<Highcharts.Series>} dirtySeries
- * Array of series with new data.
- * @param {Highcharts.Series} [newSeries]
- * If a single new series was added, a reference to this series.
- * @param {Highcharts.Point} [newPoint]
- * If a single point was added, a reference to this point.
- *
- * @return {string|null}
- * The announcement message to give to user.
- */
- buildAnnouncementMessage: function (dirtySeries, newSeries, newPoint) {
- var chart = this.chart,
- annOptions = chart.options.accessibility.announceNewData;
- // User supplied formatter?
- if (annOptions.announcementFormatter) {
- var formatterRes = annOptions.announcementFormatter(dirtySeries,
- newSeries,
- newPoint);
- if (formatterRes !== false) {
- return formatterRes.length ? formatterRes : null;
- }
- }
- // Default formatter - use lang options
- var multiple = H.charts && H.charts.length > 1 ? 'Multiple' : 'Single', langKey = newSeries ? 'newSeriesAnnounce' + multiple :
- newPoint ? 'newPointAnnounce' + multiple : 'newDataAnnounce', chartTitle = getChartTitle(chart);
- return chart.langFormat('accessibility.announceNewData.' + langKey, {
- chartTitle: chartTitle,
- seriesDesc: newSeries ?
- defaultSeriesDescriptionFormatter(newSeries) :
- null,
- pointDesc: newPoint ?
- defaultPointDescriptionFormatter(newPoint) :
- null,
- point: newPoint,
- series: newSeries
- });
- }
- });
- return NewDataAnnouncer;
- });
- _registerModule(_modules, 'Accessibility/Components/SeriesComponent/ForcedMarkers.js', [_modules['Core/Series/Series.js'], _modules['Core/Utilities.js']], function (Series, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Handle forcing series markers.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var addEvent = U.addEvent,
- merge = U.merge;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * @private
- */
- function isWithinDescriptionThreshold(series) {
- var a11yOptions = series.chart.options.accessibility;
- return series.points.length <
- a11yOptions.series.pointDescriptionEnabledThreshold ||
- a11yOptions.series.pointDescriptionEnabledThreshold === false;
- }
- /**
- * @private
- */
- function shouldForceMarkers(series) {
- var chart = series.chart,
- chartA11yEnabled = chart.options.accessibility.enabled,
- seriesA11yEnabled = (series.options.accessibility &&
- series.options.accessibility.enabled) !== false;
- return chartA11yEnabled && seriesA11yEnabled && isWithinDescriptionThreshold(series);
- }
- /**
- * @private
- */
- function hasIndividualPointMarkerOptions(series) {
- return !!(series._hasPointMarkers && series.points && series.points.length);
- }
- /**
- * @private
- */
- function unforceSeriesMarkerOptions(series) {
- var resetMarkerOptions = series.resetA11yMarkerOptions;
- if (resetMarkerOptions) {
- merge(true, series.options, {
- marker: {
- enabled: resetMarkerOptions.enabled,
- states: {
- normal: {
- opacity: resetMarkerOptions.states &&
- resetMarkerOptions.states.normal &&
- resetMarkerOptions.states.normal.opacity
- }
- }
- }
- });
- }
- }
- /**
- * @private
- */
- function forceZeroOpacityMarkerOptions(options) {
- merge(true, options, {
- marker: {
- enabled: true,
- states: {
- normal: {
- opacity: 0
- }
- }
- }
- });
- }
- /**
- * @private
- */
- function getPointMarkerOpacity(pointOptions) {
- return pointOptions.marker.states &&
- pointOptions.marker.states.normal &&
- pointOptions.marker.states.normal.opacity || 1;
- }
- /**
- * @private
- */
- function unforcePointMarkerOptions(pointOptions) {
- merge(true, pointOptions.marker, {
- states: {
- normal: {
- opacity: getPointMarkerOpacity(pointOptions)
- }
- }
- });
- }
- /**
- * @private
- */
- function handleForcePointMarkers(series) {
- var i = series.points.length;
- while (i--) {
- var point = series.points[i];
- var pointOptions = point.options;
- delete point.hasForcedA11yMarker;
- if (pointOptions.marker) {
- if (pointOptions.marker.enabled) {
- unforcePointMarkerOptions(pointOptions);
- point.hasForcedA11yMarker = false;
- }
- else {
- forceZeroOpacityMarkerOptions(pointOptions);
- point.hasForcedA11yMarker = true;
- }
- }
- }
- }
- /**
- * @private
- */
- function addForceMarkersEvents() {
- /**
- * Keep track of forcing markers.
- * @private
- */
- addEvent(Series, 'render', function () {
- var series = this,
- options = series.options;
- if (shouldForceMarkers(series)) {
- if (options.marker && options.marker.enabled === false) {
- series.a11yMarkersForced = true;
- forceZeroOpacityMarkerOptions(series.options);
- }
- if (hasIndividualPointMarkerOptions(series)) {
- handleForcePointMarkers(series);
- }
- }
- else if (series.a11yMarkersForced) {
- delete series.a11yMarkersForced;
- unforceSeriesMarkerOptions(series);
- }
- });
- /**
- * Keep track of options to reset markers to if no longer forced.
- * @private
- */
- addEvent(Series, 'afterSetOptions', function (e) {
- this.resetA11yMarkerOptions = merge(e.options.marker || {}, this.userOptions.marker || {});
- });
- /**
- * Process marker graphics after render
- * @private
- */
- addEvent(Series, 'afterRender', function () {
- var series = this;
- // For styled mode the rendered graphic does not reflect the style
- // options, and we need to add/remove classes to achieve the same.
- if (series.chart.styledMode) {
- if (series.markerGroup) {
- series.markerGroup[series.a11yMarkersForced ? 'addClass' : 'removeClass']('highcharts-a11y-markers-hidden');
- }
- // Do we need to handle individual points?
- if (hasIndividualPointMarkerOptions(series)) {
- series.points.forEach(function (point) {
- if (point.graphic) {
- point.graphic[point.hasForcedA11yMarker ? 'addClass' : 'removeClass']('highcharts-a11y-marker-hidden');
- point.graphic[point.hasForcedA11yMarker === false ? 'addClass' : 'removeClass']('highcharts-a11y-marker-visible');
- }
- });
- }
- }
- });
- }
- return addForceMarkersEvents;
- });
- _registerModule(_modules, 'Accessibility/Components/SeriesComponent/SeriesComponent.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/Components/SeriesComponent/SeriesKeyboardNavigation.js'], _modules['Accessibility/Components/SeriesComponent/NewDataAnnouncer.js'], _modules['Accessibility/Components/SeriesComponent/ForcedMarkers.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Components/SeriesComponent/SeriesDescriber.js'], _modules['Core/Tooltip.js']], function (H, U, AccessibilityComponent, SeriesKeyboardNavigation, NewDataAnnouncer, addForceMarkersEvents, ChartUtilities, SeriesDescriber, Tooltip) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Accessibility component for series and points.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var extend = U.extend;
- var hideSeriesFromAT = ChartUtilities.hideSeriesFromAT;
- var describeSeries = SeriesDescriber.describeSeries;
- // Expose functionality to users
- H.SeriesAccessibilityDescriber = SeriesDescriber;
- // Handle forcing markers
- addForceMarkersEvents();
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * The SeriesComponent class
- *
- * @private
- * @class
- * @name Highcharts.SeriesComponent
- */
- var SeriesComponent = function () { };
- SeriesComponent.prototype = new AccessibilityComponent();
- extend(SeriesComponent.prototype, /** @lends Highcharts.SeriesComponent */ {
- /**
- * Init the component.
- */
- init: function () {
- this.newDataAnnouncer = new NewDataAnnouncer(this.chart);
- this.newDataAnnouncer.init();
- this.keyboardNavigation = new SeriesKeyboardNavigation(this.chart, this.keyCodes);
- this.keyboardNavigation.init();
- this.hideTooltipFromATWhenShown();
- this.hideSeriesLabelsFromATWhenShown();
- },
- /**
- * @private
- */
- hideTooltipFromATWhenShown: function () {
- var component = this;
- this.addEvent(Tooltip, 'refresh', function () {
- if (this.chart === component.chart &&
- this.label &&
- this.label.element) {
- this.label.element.setAttribute('aria-hidden', true);
- }
- });
- },
- /**
- * @private
- */
- hideSeriesLabelsFromATWhenShown: function () {
- this.addEvent(this.chart, 'afterDrawSeriesLabels', function () {
- this.series.forEach(function (series) {
- if (series.labelBySeries) {
- series.labelBySeries.attr('aria-hidden', true);
- }
- });
- });
- },
- /**
- * Called on chart render. It is necessary to do this for render in case
- * markers change on zoom/pixel density.
- */
- onChartRender: function () {
- var chart = this.chart;
- chart.series.forEach(function (series) {
- var shouldDescribeSeries = (series.options.accessibility &&
- series.options.accessibility.enabled) !== false &&
- series.visible;
- if (shouldDescribeSeries) {
- describeSeries(series);
- }
- else {
- hideSeriesFromAT(series);
- }
- });
- },
- /**
- * Get keyboard navigation handler for this component.
- * @return {Highcharts.KeyboardNavigationHandler}
- */
- getKeyboardNavigation: function () {
- return this.keyboardNavigation.getKeyboardNavigationHandler();
- },
- /**
- * Remove traces
- */
- destroy: function () {
- this.newDataAnnouncer.destroy();
- this.keyboardNavigation.destroy();
- }
- });
- return SeriesComponent;
- });
- _registerModule(_modules, 'Accessibility/Components/ZoomComponent.js', [_modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Core/Globals.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Core/Utilities.js']], function (AccessibilityComponent, ChartUtilities, H, HTMLUtilities, KeyboardNavigationHandler, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Accessibility component for chart zoom.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
- var noop = H.noop;
- var removeElement = HTMLUtilities.removeElement,
- setElAttrs = HTMLUtilities.setElAttrs;
- var extend = U.extend,
- pick = U.pick;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * @private
- */
- function chartHasMapZoom(chart) {
- return !!(chart.mapZoom &&
- chart.mapNavButtons &&
- chart.mapNavButtons.length);
- }
- /**
- * 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);
- };
- /**
- * The ZoomComponent class
- *
- * @private
- * @class
- * @name Highcharts.ZoomComponent
- */
- var ZoomComponent = noop;
- ZoomComponent.prototype = new AccessibilityComponent();
- extend(ZoomComponent.prototype, /** @lends Highcharts.ZoomComponent */ {
- /**
- * Initialize the component
- */
- init: function () {
- var component = this,
- chart = this.chart;
- [
- 'afterShowResetZoom', 'afterDrilldown', 'drillupall'
- ].forEach(function (eventType) {
- component.addEvent(chart, eventType, function () {
- component.updateProxyOverlays();
- });
- });
- },
- /**
- * Called when chart is updated
- */
- onChartUpdate: function () {
- var chart = this.chart,
- component = this;
- // Make map zoom buttons accessible
- if (chart.mapNavButtons) {
- chart.mapNavButtons.forEach(function (button, i) {
- unhideChartElementFromAT(chart, button.element);
- component.setMapNavButtonAttrs(button.element, 'accessibility.zoom.mapZoom' + (i ? 'Out' : 'In'));
- });
- }
- },
- /**
- * @private
- * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} button
- * @param {string} labelFormatKey
- */
- setMapNavButtonAttrs: function (button, labelFormatKey) {
- var chart = this.chart,
- label = chart.langFormat(labelFormatKey, { chart: chart });
- setElAttrs(button, {
- tabindex: -1,
- role: 'button',
- 'aria-label': label
- });
- },
- /**
- * Update the proxy overlays on every new render to ensure positions are
- * correct.
- */
- onChartRender: function () {
- this.updateProxyOverlays();
- },
- /**
- * Update proxy overlays, recreating the buttons.
- */
- updateProxyOverlays: function () {
- var chart = this.chart;
- // Always start with a clean slate
- removeElement(this.drillUpProxyGroup);
- removeElement(this.resetZoomProxyGroup);
- if (chart.resetZoomButton) {
- this.recreateProxyButtonAndGroup(chart.resetZoomButton, 'resetZoomProxyButton', 'resetZoomProxyGroup', chart.langFormat('accessibility.zoom.resetZoomButton', { chart: chart }));
- }
- if (chart.drillUpButton) {
- this.recreateProxyButtonAndGroup(chart.drillUpButton, 'drillUpProxyButton', 'drillUpProxyGroup', chart.langFormat('accessibility.drillUpButton', {
- chart: chart,
- buttonText: chart.getDrilldownBackText()
- }));
- }
- },
- /**
- * @private
- * @param {Highcharts.SVGElement} buttonEl
- * @param {string} buttonProp
- * @param {string} groupProp
- * @param {string} label
- */
- recreateProxyButtonAndGroup: function (buttonEl, buttonProp, groupProp, label) {
- removeElement(this[groupProp]);
- this[groupProp] = this.addProxyGroup();
- this[buttonProp] = this.createProxyButton(buttonEl, this[groupProp], { 'aria-label': label, tabindex: -1 });
- },
- /**
- * Get keyboard navigation handler for map zoom.
- * @private
- * @return {Highcharts.KeyboardNavigationHandler} The module object
- */
- getMapZoomNavigation: function () {
- var keys = this.keyCodes,
- chart = this.chart,
- component = this;
- return new KeyboardNavigationHandler(chart, {
- keyCodeMap: [
- [
- [keys.up, keys.down, keys.left, keys.right],
- function (keyCode) {
- return component.onMapKbdArrow(this, keyCode);
- }
- ],
- [
- [keys.tab],
- function (_keyCode, e) {
- return component.onMapKbdTab(this, e);
- }
- ],
- [
- [keys.space, keys.enter],
- function () {
- return component.onMapKbdClick(this);
- }
- ]
- ],
- validate: function () {
- return chartHasMapZoom(chart);
- },
- init: function (direction) {
- return component.onMapNavInit(direction);
- }
- });
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @param {number} keyCode
- * @return {number} Response code
- */
- onMapKbdArrow: function (keyboardNavigationHandler, keyCode) {
- var keys = this.keyCodes,
- panAxis = (keyCode === keys.up || keyCode === keys.down) ?
- 'yAxis' : 'xAxis',
- stepDirection = (keyCode === keys.left || keyCode === keys.up) ?
- -1 : 1;
- this.chart[panAxis][0].panStep(stepDirection);
- return keyboardNavigationHandler.response.success;
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @param {global.KeyboardEvent} event
- * @return {number} Response code
- */
- onMapKbdTab: function (keyboardNavigationHandler, event) {
- var button,
- chart = this.chart,
- response = keyboardNavigationHandler.response,
- isBackwards = event.shiftKey,
- isMoveOutOfRange = isBackwards && !this.focusedMapNavButtonIx ||
- !isBackwards && this.focusedMapNavButtonIx;
- // Deselect old
- chart.mapNavButtons[this.focusedMapNavButtonIx].setState(0);
- if (isMoveOutOfRange) {
- chart.mapZoom(); // Reset zoom
- return response[isBackwards ? 'prev' : 'next'];
- }
- // Select other button
- this.focusedMapNavButtonIx += isBackwards ? -1 : 1;
- button = chart.mapNavButtons[this.focusedMapNavButtonIx];
- chart.setFocusToElement(button.box, button.element);
- button.setState(2);
- return response.success;
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @return {number} Response code
- */
- onMapKbdClick: function (keyboardNavigationHandler) {
- this.fakeClickEvent(this.chart.mapNavButtons[this.focusedMapNavButtonIx]
- .element);
- return keyboardNavigationHandler.response.success;
- },
- /**
- * @private
- * @param {number} direction
- */
- onMapNavInit: function (direction) {
- var chart = this.chart,
- zoomIn = chart.mapNavButtons[0],
- zoomOut = chart.mapNavButtons[1],
- initialButton = direction > 0 ? zoomIn : zoomOut;
- chart.setFocusToElement(initialButton.box, initialButton.element);
- initialButton.setState(2);
- this.focusedMapNavButtonIx = direction > 0 ? 0 : 1;
- },
- /**
- * Get keyboard navigation handler for a simple chart button. Provide the
- * button reference for the chart, and a function to call on click.
- *
- * @private
- * @param {string} buttonProp The property on chart referencing the button.
- * @return {Highcharts.KeyboardNavigationHandler} The module object
- */
- simpleButtonNavigation: function (buttonProp, proxyProp, onClick) {
- var keys = this.keyCodes,
- component = this,
- chart = this.chart;
- return new KeyboardNavigationHandler(chart, {
- keyCodeMap: [
- [
- [keys.tab, keys.up, keys.down, keys.left, keys.right],
- function (keyCode, e) {
- var isBackwards = keyCode === keys.tab && e.shiftKey ||
- keyCode === keys.left || keyCode === keys.up;
- // Arrow/tab => just move
- return this.response[isBackwards ? 'prev' : 'next'];
- }
- ],
- [
- [keys.space, keys.enter],
- function () {
- var res = onClick(this,
- chart);
- return pick(res, this.response.success);
- }
- ]
- ],
- validate: function () {
- var hasButton = (chart[buttonProp] &&
- chart[buttonProp].box &&
- component[proxyProp]);
- return hasButton;
- },
- init: function () {
- chart.setFocusToElement(chart[buttonProp].box, component[proxyProp]);
- }
- });
- },
- /**
- * Get keyboard navigation handlers for this component.
- * @return {Array<Highcharts.KeyboardNavigationHandler>}
- * List of module objects
- */
- getKeyboardNavigation: function () {
- return [
- this.simpleButtonNavigation('resetZoomButton', 'resetZoomProxyButton', function (_handler, chart) {
- chart.zoomOut();
- }),
- this.simpleButtonNavigation('drillUpButton', 'drillUpProxyButton', function (handler, chart) {
- chart.drillUp();
- return handler.response.prev;
- }),
- this.getMapZoomNavigation()
- ];
- }
- });
- return ZoomComponent;
- });
- _registerModule(_modules, 'Extensions/RangeSelector.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Color/Palette.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (Axis, Chart, H, D, palette, SVGElement, U) {
- /* *
- *
- * (c) 2010-2021 Torstein Honsi
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var defaultOptions = D.defaultOptions;
- var addEvent = U.addEvent,
- createElement = U.createElement,
- css = U.css,
- defined = U.defined,
- destroyObjectProperties = U.destroyObjectProperties,
- discardElement = U.discardElement,
- extend = U.extend,
- find = U.find,
- fireEvent = U.fireEvent,
- isNumber = U.isNumber,
- merge = U.merge,
- objectEach = U.objectEach,
- pad = U.pad,
- pick = U.pick,
- pInt = U.pInt,
- splat = U.splat;
- /**
- * Define the time span for the button
- *
- * @typedef {"all"|"day"|"hour"|"millisecond"|"minute"|"month"|"second"|"week"|"year"|"ytd"} Highcharts.RangeSelectorButtonTypeValue
- */
- /**
- * Callback function to react on button clicks.
- *
- * @callback Highcharts.RangeSelectorClickCallbackFunction
- *
- * @param {global.Event} e
- * Event arguments.
- *
- * @param {boolean|undefined}
- * Return false to cancel the default button event.
- */
- /**
- * Callback function to parse values entered in the input boxes and return a
- * valid JavaScript time as milliseconds since 1970.
- *
- * @callback Highcharts.RangeSelectorParseCallbackFunction
- *
- * @param {string} value
- * Input value to parse.
- *
- * @return {number}
- * Parsed JavaScript time value.
- */
- /* ************************************************************************** *
- * Start Range Selector code *
- * ************************************************************************** */
- extend(defaultOptions, {
- /**
- * The range selector is a tool for selecting ranges to display within
- * the chart. It provides buttons to select preconfigured ranges in
- * the chart, like 1 day, 1 week, 1 month etc. It also provides input
- * boxes where min and max dates can be manually input.
- *
- * @product highstock gantt
- * @optionparent rangeSelector
- */
- rangeSelector: {
- /**
- * Whether to enable all buttons from the start. By default buttons are
- * only enabled if the corresponding time range exists on the X axis,
- * but enabling all buttons allows for dynamically loading different
- * time ranges.
- *
- * @sample {highstock} stock/rangeselector/allbuttonsenabled-true/
- * All buttons enabled
- *
- * @since 2.0.3
- */
- allButtonsEnabled: false,
- /**
- * An array of configuration objects for the buttons.
- *
- * Defaults to:
- * ```js
- * buttons: [{
- * type: 'month',
- * count: 1,
- * text: '1m',
- * title: 'View 1 month'
- * }, {
- * type: 'month',
- * count: 3,
- * text: '3m',
- * title: 'View 3 months'
- * }, {
- * type: 'month',
- * count: 6,
- * text: '6m',
- * title: 'View 6 months'
- * }, {
- * type: 'ytd',
- * text: 'YTD',
- * title: 'View year to date'
- * }, {
- * type: 'year',
- * count: 1,
- * text: '1y',
- * title: 'View 1 year'
- * }, {
- * type: 'all',
- * text: 'All',
- * title: 'View all'
- * }]
- * ```
- *
- * @sample {highstock} stock/rangeselector/datagrouping/
- * Data grouping by buttons
- *
- * @type {Array<*>}
- */
- buttons: void 0,
- /**
- * How many units of the defined type the button should span. If `type`
- * is "month" and `count` is 3, the button spans three months.
- *
- * @type {number}
- * @default 1
- * @apioption rangeSelector.buttons.count
- */
- /**
- * Fires when clicking on the rangeSelector button. One parameter,
- * event, is passed to the function, containing common event
- * information.
- *
- * ```js
- * click: function(e) {
- * console.log(this);
- * }
- * ```
- *
- * Return false to stop default button's click action.
- *
- * @sample {highstock} stock/rangeselector/button-click/
- * Click event on the button
- *
- * @type {Highcharts.RangeSelectorClickCallbackFunction}
- * @apioption rangeSelector.buttons.events.click
- */
- /**
- * Additional range (in milliseconds) added to the end of the calculated
- * time span.
- *
- * @sample {highstock} stock/rangeselector/min-max-offsets/
- * Button offsets
- *
- * @type {number}
- * @default 0
- * @since 6.0.0
- * @apioption rangeSelector.buttons.offsetMax
- */
- /**
- * Additional range (in milliseconds) added to the start of the
- * calculated time span.
- *
- * @sample {highstock} stock/rangeselector/min-max-offsets/
- * Button offsets
- *
- * @type {number}
- * @default 0
- * @since 6.0.0
- * @apioption rangeSelector.buttons.offsetMin
- */
- /**
- * When buttons apply dataGrouping on a series, by default zooming
- * in/out will deselect buttons and unset dataGrouping. Enable this
- * option to keep buttons selected when extremes change.
- *
- * @sample {highstock} stock/rangeselector/preserve-datagrouping/
- * Different preserveDataGrouping settings
- *
- * @type {boolean}
- * @default false
- * @since 6.1.2
- * @apioption rangeSelector.buttons.preserveDataGrouping
- */
- /**
- * A custom data grouping object for each button.
- *
- * @see [series.dataGrouping](#plotOptions.series.dataGrouping)
- *
- * @sample {highstock} stock/rangeselector/datagrouping/
- * Data grouping by range selector buttons
- *
- * @type {*}
- * @extends plotOptions.series.dataGrouping
- * @apioption rangeSelector.buttons.dataGrouping
- */
- /**
- * The text for the button itself.
- *
- * @type {string}
- * @apioption rangeSelector.buttons.text
- */
- /**
- * Explanation for the button, shown as a tooltip on hover, and used by
- * assistive technology.
- *
- * @type {string}
- * @apioption rangeSelector.buttons.title
- */
- /**
- * Defined the time span for the button. Can be one of `millisecond`,
- * `second`, `minute`, `hour`, `day`, `week`, `month`, `year`, `ytd`,
- * and `all`.
- *
- * @type {Highcharts.RangeSelectorButtonTypeValue}
- * @apioption rangeSelector.buttons.type
- */
- /**
- * The space in pixels between the buttons in the range selector.
- */
- buttonSpacing: 5,
- /**
- * Whether to collapse the range selector buttons into a dropdown when
- * there is not enough room to show everything in a single row, instead
- * of dividing the range selector into multiple rows.
- * Can be one of the following:
- * - `always`: Always collapse
- * - `responsive`: Only collapse when there is not enough room
- * - `never`: Never collapse
- *
- * @sample {highstock} stock/rangeselector/dropdown/
- * Dropdown option
- *
- * @validvalue ["always", "responsive", "never"]
- * @since 9.0.0
- */
- dropdown: 'responsive',
- /**
- * Enable or disable the range selector. Default to `true` for stock
- * charts, using the `stockChart` factory.
- *
- * @sample {highstock} stock/rangeselector/enabled/
- * Disable the range selector
- *
- * @type {boolean|undefined}
- * @default {highstock} true
- */
- enabled: void 0,
- /**
- * The vertical alignment of the rangeselector box. Allowed properties
- * are `top`, `middle`, `bottom`.
- *
- * @sample {highstock} stock/rangeselector/vertical-align-middle/
- * Middle
- * @sample {highstock} stock/rangeselector/vertical-align-bottom/
- * Bottom
- *
- * @type {Highcharts.VerticalAlignValue}
- * @since 6.0.0
- */
- verticalAlign: 'top',
- /**
- * A collection of attributes for the buttons. The object takes SVG
- * attributes like `fill`, `stroke`, `stroke-width`, as well as `style`,
- * a collection of CSS properties for the text.
- *
- * The object can also be extended with states, so you can set
- * presentational options for `hover`, `select` or `disabled` button
- * states.
- *
- * CSS styles for the text label.
- *
- * In styled mode, the buttons are styled by the
- * `.highcharts-range-selector-buttons .highcharts-button` rule with its
- * different states.
- *
- * @sample {highstock} stock/rangeselector/styling/
- * Styling the buttons and inputs
- *
- * @type {Highcharts.SVGAttributes}
- */
- buttonTheme: {
- /** @ignore */
- width: 28,
- /** @ignore */
- height: 18,
- /** @ignore */
- padding: 2,
- /** @ignore */
- zIndex: 7 // #484, #852
- },
- /**
- * When the rangeselector is floating, the plot area does not reserve
- * space for it. This opens for positioning anywhere on the chart.
- *
- * @sample {highstock} stock/rangeselector/floating/
- * Placing the range selector between the plot area and the
- * navigator
- *
- * @since 6.0.0
- */
- floating: false,
- /**
- * The x offset of the range selector relative to its horizontal
- * alignment within `chart.spacingLeft` and `chart.spacingRight`.
- *
- * @since 6.0.0
- */
- x: 0,
- /**
- * The y offset of the range selector relative to its horizontal
- * alignment within `chart.spacingLeft` and `chart.spacingRight`.
- *
- * @since 6.0.0
- */
- y: 0,
- /**
- * Deprecated. The height of the range selector. Currently it is
- * calculated dynamically.
- *
- * @deprecated
- * @type {number|undefined}
- * @since 2.1.9
- */
- height: void 0,
- /**
- * The border color of the date input boxes.
- *
- * @sample {highstock} stock/rangeselector/styling/
- * Styling the buttons and inputs
- *
- * @type {Highcharts.ColorString}
- * @since 1.3.7
- */
- inputBoxBorderColor: 'none',
- /**
- * The pixel height of the date input boxes.
- *
- * @sample {highstock} stock/rangeselector/styling/
- * Styling the buttons and inputs
- *
- * @since 1.3.7
- */
- inputBoxHeight: 17,
- /**
- * The pixel width of the date input boxes. When `undefined`, the width
- * is fitted to the rendered content.
- *
- * @sample {highstock} stock/rangeselector/styling/
- * Styling the buttons and inputs
- *
- * @type {number|undefined}
- * @since 1.3.7
- */
- inputBoxWidth: void 0,
- /**
- * The date format in the input boxes when not selected for editing.
- * Defaults to `%b %e, %Y`.
- *
- * This is used to determine which type of input to show,
- * `datetime-local`, `date` or `time` and falling back to `text` when
- * the browser does not support the input type or the format contains
- * milliseconds.
- *
- * @sample {highstock} stock/rangeselector/input-type/
- * Input types
- * @sample {highstock} stock/rangeselector/input-format/
- * Milliseconds in the range selector
- *
- */
- inputDateFormat: '%b %e, %Y',
- /**
- * A custom callback function to parse values entered in the input boxes
- * and return a valid JavaScript time as milliseconds since 1970.
- * The first argument passed is a value to parse,
- * second is a boolean indicating use of the UTC time.
- *
- * This will only get called for inputs of type `text`. Since v8.2.3,
- * the input type is dynamically determined based on the granularity
- * of the `inputDateFormat` and the browser support.
- *
- * @sample {highstock} stock/rangeselector/input-format/
- * Milliseconds in the range selector
- *
- * @type {Highcharts.RangeSelectorParseCallbackFunction}
- * @since 1.3.3
- */
- inputDateParser: void 0,
- /**
- * The date format in the input boxes when they are selected for
- * editing. This must be a format that is recognized by JavaScript
- * Date.parse.
- *
- * This will only be used for inputs of type `text`. Since v8.2.3,
- * the input type is dynamically determined based on the granularity
- * of the `inputDateFormat` and the browser support.
- *
- * @sample {highstock} stock/rangeselector/input-format/
- * Milliseconds in the range selector
- *
- */
- inputEditDateFormat: '%Y-%m-%d',
- /**
- * Enable or disable the date input boxes.
- */
- inputEnabled: true,
- /**
- * Positioning for the input boxes. Allowed properties are `align`,
- * `x` and `y`.
- *
- * @since 1.2.4
- */
- inputPosition: {
- /**
- * The alignment of the input box. Allowed properties are `left`,
- * `center`, `right`.
- *
- * @sample {highstock} stock/rangeselector/input-button-position/
- * Alignment
- *
- * @type {Highcharts.AlignValue}
- * @since 6.0.0
- */
- align: 'right',
- /**
- * X offset of the input row.
- */
- x: 0,
- /**
- * Y offset of the input row.
- */
- y: 0
- },
- /**
- * The space in pixels between the labels and the date input boxes in
- * the range selector.
- *
- * @since 9.0.0
- */
- inputSpacing: 5,
- /**
- * The index of the button to appear pre-selected.
- *
- * @type {number}
- */
- selected: void 0,
- /**
- * Positioning for the button row.
- *
- * @since 1.2.4
- */
- buttonPosition: {
- /**
- * The alignment of the input box. Allowed properties are `left`,
- * `center`, `right`.
- *
- * @sample {highstock} stock/rangeselector/input-button-position/
- * Alignment
- *
- * @type {Highcharts.AlignValue}
- * @since 6.0.0
- */
- align: 'left',
- /**
- * X offset of the button row.
- */
- x: 0,
- /**
- * Y offset of the button row.
- */
- y: 0
- },
- /**
- * CSS for the HTML inputs in the range selector.
- *
- * In styled mode, the inputs are styled by the
- * `.highcharts-range-input text` rule in SVG mode, and
- * `input.highcharts-range-selector` when active.
- *
- * @sample {highstock} stock/rangeselector/styling/
- * Styling the buttons and inputs
- *
- * @type {Highcharts.CSSObject}
- * @apioption rangeSelector.inputStyle
- */
- inputStyle: {
- /** @ignore */
- color: palette.highlightColor80,
- /** @ignore */
- cursor: 'pointer'
- },
- /**
- * CSS styles for the labels - the Zoom, From and To texts.
- *
- * In styled mode, the labels are styled by the
- * `.highcharts-range-label` class.
- *
- * @sample {highstock} stock/rangeselector/styling/
- * Styling the buttons and inputs
- *
- * @type {Highcharts.CSSObject}
- */
- labelStyle: {
- /** @ignore */
- color: palette.neutralColor60
- }
- }
- });
- extend(defaultOptions.lang,
- /**
- * Language object. The language object is global and it can't be set
- * on each chart initialization. Instead, use `Highcharts.setOptions` to
- * set it before any chart is initialized.
- *
- * ```js
- * Highcharts.setOptions({
- * lang: {
- * months: [
- * 'Janvier', 'Février', 'Mars', 'Avril',
- * 'Mai', 'Juin', 'Juillet', 'Août',
- * 'Septembre', 'Octobre', 'Novembre', 'Décembre'
- * ],
- * weekdays: [
- * 'Dimanche', 'Lundi', 'Mardi', 'Mercredi',
- * 'Jeudi', 'Vendredi', 'Samedi'
- * ]
- * }
- * });
- * ```
- *
- * @optionparent lang
- */
- {
- /**
- * The text for the label for the range selector buttons.
- *
- * @product highstock gantt
- */
- rangeSelectorZoom: 'Zoom',
- /**
- * The text for the label for the "from" input box in the range
- * selector. Since v9.0, this string is empty as the label is not
- * rendered by default.
- *
- * @product highstock gantt
- */
- rangeSelectorFrom: '',
- /**
- * The text for the label for the "to" input box in the range selector.
- *
- * @product highstock gantt
- */
- rangeSelectorTo: '→'
- });
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * The range selector.
- *
- * @private
- * @class
- * @name Highcharts.RangeSelector
- * @param {Highcharts.Chart} chart
- */
- var RangeSelector = /** @class */ (function () {
- function RangeSelector(chart) {
- /* *
- *
- * Properties
- *
- * */
- this.buttons = void 0;
- this.buttonOptions = RangeSelector.prototype.defaultButtons;
- this.initialButtonGroupWidth = 0;
- this.options = void 0;
- this.chart = chart;
- // Run RangeSelector
- this.init(chart);
- }
- /**
- * The method to run when one of the buttons in the range selectors is
- * clicked
- *
- * @private
- * @function Highcharts.RangeSelector#clickButton
- * @param {number} i
- * The index of the button
- * @param {boolean} [redraw]
- * @return {void}
- */
- RangeSelector.prototype.clickButton = function (i, redraw) {
- var rangeSelector = this,
- chart = rangeSelector.chart,
- rangeOptions = rangeSelector.buttonOptions[i],
- baseAxis = chart.xAxis[0],
- unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis || {},
- dataMin = unionExtremes.dataMin,
- dataMax = unionExtremes.dataMax,
- newMin,
- newMax = baseAxis && Math.round(Math.min(baseAxis.max,
- pick(dataMax,
- baseAxis.max))), // #1568
- type = rangeOptions.type,
- baseXAxisOptions,
- range = rangeOptions._range,
- rangeMin,
- minSetting,
- rangeSetting,
- ctx,
- ytdExtremes,
- dataGrouping = rangeOptions.dataGrouping;
- // chart has no data, base series is removed
- if (dataMin === null || dataMax === null) {
- return;
- }
- // Set the fixed range before range is altered
- chart.fixedRange = range;
- rangeSelector.setSelected(i);
- // Apply dataGrouping associated to button
- if (dataGrouping) {
- this.forcedDataGrouping = true;
- Axis.prototype.setDataGrouping.call(baseAxis || { chart: this.chart }, dataGrouping, false);
- this.frozenStates = rangeOptions.preserveDataGrouping;
- }
- // Apply range
- if (type === 'month' || type === 'year') {
- if (!baseAxis) {
- // This is set to the user options and picked up later when the
- // axis is instantiated so that we know the min and max.
- range = rangeOptions;
- }
- else {
- ctx = {
- range: rangeOptions,
- max: newMax,
- chart: chart,
- dataMin: dataMin,
- dataMax: dataMax
- };
- newMin = baseAxis.minFromRange.call(ctx);
- if (isNumber(ctx.newMax)) {
- newMax = ctx.newMax;
- }
- }
- // Fixed times like minutes, hours, days
- }
- else if (range) {
- newMin = Math.max(newMax - range, dataMin);
- newMax = Math.min(newMin + range, dataMax);
- }
- else if (type === 'ytd') {
- // On user clicks on the buttons, or a delayed action running from
- // the beforeRender event (below), the baseAxis is defined.
- if (baseAxis) {
- // When "ytd" is the pre-selected button for the initial view,
- // its calculation is delayed and rerun in the beforeRender
- // event (below). When the series are initialized, but before
- // the chart is rendered, we have access to the xData array
- // (#942).
- if (typeof dataMax === 'undefined') {
- dataMin = Number.MAX_VALUE;
- dataMax = Number.MIN_VALUE;
- chart.series.forEach(function (series) {
- // reassign it to the last item
- var xData = series.xData;
- dataMin = Math.min(xData[0], dataMin);
- dataMax = Math.max(xData[xData.length - 1], dataMax);
- });
- redraw = false;
- }
- ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, chart.time.useUTC);
- newMin = rangeMin = ytdExtremes.min;
- newMax = ytdExtremes.max;
- // "ytd" is pre-selected. We don't yet have access to processed
- // point and extremes data (things like pointStart and pointInterval
- // are missing), so we delay the process (#942)
- }
- else {
- rangeSelector.deferredYTDClick = i;
- return;
- }
- }
- else if (type === 'all' && baseAxis) {
- // If the navigator exist and the axis range is declared reset that
- // range and from now on only use the range set by a user, #14742.
- if (chart.navigator && chart.navigator.baseSeries[0]) {
- chart.navigator.baseSeries[0].xAxis.options.range = void 0;
- }
- newMin = dataMin;
- newMax = dataMax;
- }
- if (defined(newMin)) {
- newMin += rangeOptions._offsetMin;
- }
- if (defined(newMax)) {
- newMax += rangeOptions._offsetMax;
- }
- if (this.dropdown) {
- this.dropdown.selectedIndex = i + 1;
- }
- // Update the chart
- if (!baseAxis) {
- // Axis not yet instanciated. Temporarily set min and range
- // options and remove them on chart load (#4317).
- baseXAxisOptions = splat(chart.options.xAxis)[0];
- rangeSetting = baseXAxisOptions.range;
- baseXAxisOptions.range = range;
- minSetting = baseXAxisOptions.min;
- baseXAxisOptions.min = rangeMin;
- addEvent(chart, 'load', function resetMinAndRange() {
- baseXAxisOptions.range = rangeSetting;
- baseXAxisOptions.min = minSetting;
- });
- }
- else {
- // Existing axis object. Set extremes after render time.
- baseAxis.setExtremes(newMin, newMax, pick(redraw, true), void 0, // auto animation
- {
- trigger: 'rangeSelectorButton',
- rangeSelectorButton: rangeOptions
- });
- }
- fireEvent(this, 'afterBtnClick');
- };
- /**
- * Set the selected option. This method only sets the internal flag, it
- * doesn't update the buttons or the actual zoomed range.
- *
- * @private
- * @function Highcharts.RangeSelector#setSelected
- * @param {number} [selected]
- * @return {void}
- */
- RangeSelector.prototype.setSelected = function (selected) {
- this.selected = this.options.selected = selected;
- };
- /**
- * Initialize the range selector
- *
- * @private
- * @function Highcharts.RangeSelector#init
- * @param {Highcharts.Chart} chart
- * @return {void}
- */
- RangeSelector.prototype.init = function (chart) {
- var rangeSelector = this,
- options = chart.options.rangeSelector,
- buttonOptions = options.buttons || rangeSelector.defaultButtons.slice(),
- selectedOption = options.selected,
- blurInputs = function () {
- var minInput = rangeSelector.minInput,
- maxInput = rangeSelector.maxInput;
- // #3274 in some case blur is not defined
- if (minInput && minInput.blur) {
- fireEvent(minInput, 'blur');
- }
- if (maxInput && maxInput.blur) {
- fireEvent(maxInput, 'blur');
- }
- };
- rangeSelector.chart = chart;
- rangeSelector.options = options;
- rangeSelector.buttons = [];
- rangeSelector.buttonOptions = buttonOptions;
- this.eventsToUnbind = [];
- this.eventsToUnbind.push(addEvent(chart.container, 'mousedown', blurInputs));
- this.eventsToUnbind.push(addEvent(chart, 'resize', blurInputs));
- // Extend the buttonOptions with actual range
- buttonOptions.forEach(rangeSelector.computeButtonRange);
- // zoomed range based on a pre-selected button index
- if (typeof selectedOption !== 'undefined' &&
- buttonOptions[selectedOption]) {
- this.clickButton(selectedOption, false);
- }
- this.eventsToUnbind.push(addEvent(chart, 'load', function () {
- // If a data grouping is applied to the current button, release it
- // when extremes change
- if (chart.xAxis && chart.xAxis[0]) {
- addEvent(chart.xAxis[0], 'setExtremes', function (e) {
- if (this.max - this.min !==
- chart.fixedRange &&
- e.trigger !== 'rangeSelectorButton' &&
- e.trigger !== 'updatedData' &&
- rangeSelector.forcedDataGrouping &&
- !rangeSelector.frozenStates) {
- this.setDataGrouping(false, false);
- }
- });
- }
- }));
- };
- /**
- * Dynamically update the range selector buttons after a new range has been
- * set
- *
- * @private
- * @function Highcharts.RangeSelector#updateButtonStates
- * @return {void}
- */
- RangeSelector.prototype.updateButtonStates = function () {
- var rangeSelector = this,
- chart = this.chart,
- dropdown = this.dropdown,
- baseAxis = chart.xAxis[0],
- actualRange = Math.round(baseAxis.max - baseAxis.min),
- hasNoData = !baseAxis.hasVisibleSeries,
- day = 24 * 36e5, // A single day in milliseconds
- unionExtremes = (chart.scroller &&
- chart.scroller.getUnionExtremes()) || baseAxis,
- dataMin = unionExtremes.dataMin,
- dataMax = unionExtremes.dataMax,
- ytdExtremes = rangeSelector.getYTDExtremes(dataMax,
- dataMin,
- chart.time.useUTC),
- ytdMin = ytdExtremes.min,
- ytdMax = ytdExtremes.max,
- selected = rangeSelector.selected,
- selectedExists = isNumber(selected),
- allButtonsEnabled = rangeSelector.options.allButtonsEnabled,
- buttons = rangeSelector.buttons;
- rangeSelector.buttonOptions.forEach(function (rangeOptions, i) {
- var range = rangeOptions._range,
- type = rangeOptions.type,
- count = rangeOptions.count || 1,
- button = buttons[i],
- state = 0,
- disable,
- select,
- offsetRange = rangeOptions._offsetMax -
- rangeOptions._offsetMin,
- isSelected = i === selected,
- // Disable buttons where the range exceeds what is allowed in
- // the current view
- isTooGreatRange = range >
- dataMax - dataMin,
- // Disable buttons where the range is smaller than the minimum
- // range
- isTooSmallRange = range < baseAxis.minRange,
- // Do not select the YTD button if not explicitly told so
- isYTDButNotSelected = false,
- // Disable the All button if we're already showing all
- isAllButAlreadyShowingAll = false,
- isSameRange = range === actualRange;
- // Months and years have a variable range so we check the extremes
- if ((type === 'month' || type === 'year') &&
- (actualRange + 36e5 >=
- { month: 28, year: 365 }[type] * day * count - offsetRange) &&
- (actualRange - 36e5 <=
- { month: 31, year: 366 }[type] * day * count + offsetRange)) {
- isSameRange = true;
- }
- else if (type === 'ytd') {
- isSameRange = (ytdMax - ytdMin + offsetRange) === actualRange;
- isYTDButNotSelected = !isSelected;
- }
- else if (type === 'all') {
- isSameRange = (baseAxis.max - baseAxis.min >=
- dataMax - dataMin);
- isAllButAlreadyShowingAll = (!isSelected &&
- selectedExists &&
- isSameRange);
- }
- // The new zoom area happens to match the range for a button - mark
- // it selected. This happens when scrolling across an ordinal gap.
- // It can be seen in the intraday demos when selecting 1h and scroll
- // across the night gap.
- disable = (!allButtonsEnabled &&
- (isTooGreatRange ||
- isTooSmallRange ||
- isAllButAlreadyShowingAll ||
- hasNoData));
- select = ((isSelected && isSameRange) ||
- (isSameRange && !selectedExists && !isYTDButNotSelected) ||
- (isSelected && rangeSelector.frozenStates));
- if (disable) {
- state = 3;
- }
- else if (select) {
- selectedExists = true; // Only one button can be selected
- state = 2;
- }
- // If state has changed, update the button
- if (button.state !== state) {
- button.setState(state);
- if (dropdown) {
- dropdown.options[i + 1].disabled = disable;
- if (state === 2) {
- dropdown.selectedIndex = i + 1;
- }
- }
- // Reset (#9209)
- if (state === 0 && selected === i) {
- rangeSelector.setSelected();
- }
- }
- });
- };
- /**
- * Compute and cache the range for an individual button
- *
- * @private
- * @function Highcharts.RangeSelector#computeButtonRange
- * @param {Highcharts.RangeSelectorButtonsOptions} rangeOptions
- * @return {void}
- */
- RangeSelector.prototype.computeButtonRange = function (rangeOptions) {
- var type = rangeOptions.type,
- count = rangeOptions.count || 1,
- // these time intervals have a fixed number of milliseconds, as
- // opposed to month, ytd and year
- fixedTimes = {
- millisecond: 1,
- second: 1000,
- minute: 60 * 1000,
- hour: 3600 * 1000,
- day: 24 * 3600 * 1000,
- week: 7 * 24 * 3600 * 1000
- };
- // Store the range on the button object
- if (fixedTimes[type]) {
- rangeOptions._range = fixedTimes[type] * count;
- }
- else if (type === 'month' || type === 'year') {
- rangeOptions._range = {
- month: 30,
- year: 365
- }[type] * 24 * 36e5 * count;
- }
- rangeOptions._offsetMin = pick(rangeOptions.offsetMin, 0);
- rangeOptions._offsetMax = pick(rangeOptions.offsetMax, 0);
- rangeOptions._range +=
- rangeOptions._offsetMax - rangeOptions._offsetMin;
- };
- /**
- * Get the unix timestamp of a HTML input for the dates
- *
- * @private
- * @function Highcharts.RangeSelector#getInputValue
- * @param {string} name
- * @return {number}
- */
- RangeSelector.prototype.getInputValue = function (name) {
- var input = name === 'min' ? this.minInput : this.maxInput;
- var options = this.chart.options.rangeSelector;
- var time = this.chart.time;
- if (input) {
- return ((input.type === 'text' && options.inputDateParser) ||
- this.defaultInputDateParser)(input.value, time.useUTC, time);
- }
- return 0;
- };
- /**
- * Set the internal and displayed value of a HTML input for the dates
- *
- * @private
- * @function Highcharts.RangeSelector#setInputValue
- * @param {string} name
- * @param {number} [inputTime]
- * @return {void}
- */
- RangeSelector.prototype.setInputValue = function (name, inputTime) {
- var options = this.options, time = this.chart.time, input = name === 'min' ? this.minInput : this.maxInput, dateBox = name === 'min' ? this.minDateBox : this.maxDateBox;
- if (input) {
- var hcTimeAttr = input.getAttribute('data-hc-time');
- var updatedTime = defined(hcTimeAttr) ? Number(hcTimeAttr) : void 0;
- if (defined(inputTime)) {
- var previousTime = updatedTime;
- if (defined(previousTime)) {
- input.setAttribute('data-hc-time-previous', previousTime);
- }
- input.setAttribute('data-hc-time', inputTime);
- updatedTime = inputTime;
- }
- input.value = time.dateFormat(this.inputTypeFormats[input.type] || options.inputEditDateFormat, updatedTime);
- if (dateBox) {
- dateBox.attr({
- text: time.dateFormat(options.inputDateFormat, updatedTime)
- });
- }
- }
- };
- /**
- * Set the min and max value of a HTML input for the dates
- *
- * @private
- * @function Highcharts.RangeSelector#setInputExtremes
- * @param {string} name
- * @param {number} min
- * @param {number} max
- * @return {void}
- */
- RangeSelector.prototype.setInputExtremes = function (name, min, max) {
- var input = name === 'min' ? this.minInput : this.maxInput;
- if (input) {
- var format = this.inputTypeFormats[input.type];
- var time = this.chart.time;
- if (format) {
- var newMin = time.dateFormat(format,
- min);
- if (input.min !== newMin) {
- input.min = newMin;
- }
- var newMax = time.dateFormat(format,
- max);
- if (input.max !== newMax) {
- input.max = newMax;
- }
- }
- }
- };
- /**
- * @private
- * @function Highcharts.RangeSelector#showInput
- * @param {string} name
- * @return {void}
- */
- RangeSelector.prototype.showInput = function (name) {
- var dateBox = name === 'min' ? this.minDateBox : this.maxDateBox;
- var input = name === 'min' ? this.minInput : this.maxInput;
- if (input && dateBox && this.inputGroup) {
- var isTextInput = input.type === 'text';
- var _a = this.inputGroup,
- translateX = _a.translateX,
- translateY = _a.translateY;
- var inputBoxWidth = this.options.inputBoxWidth;
- css(input, {
- width: isTextInput ? ((dateBox.width + (inputBoxWidth ? -2 : 20)) + 'px') : 'auto',
- height: isTextInput ? ((dateBox.height - 2) + 'px') : 'auto',
- border: '2px solid silver'
- });
- if (isTextInput && inputBoxWidth) {
- css(input, {
- left: (translateX + dateBox.x) + 'px',
- top: translateY + 'px'
- });
- // Inputs of types date, time or datetime-local should be centered
- // on top of the dateBox
- }
- else {
- css(input, {
- left: Math.min(Math.round(dateBox.x +
- translateX -
- (input.offsetWidth - dateBox.width) / 2), this.chart.chartWidth - input.offsetWidth) + 'px',
- top: (translateY - (input.offsetHeight - dateBox.height) / 2) + 'px'
- });
- }
- }
- };
- /**
- * @private
- * @function Highcharts.RangeSelector#hideInput
- * @param {string} name
- * @return {void}
- */
- RangeSelector.prototype.hideInput = function (name) {
- var input = name === 'min' ? this.minInput : this.maxInput;
- if (input) {
- css(input, {
- top: '-9999em',
- border: 0,
- width: '1px',
- height: '1px'
- });
- }
- };
- /**
- * @private
- * @function Highcharts.RangeSelector#defaultInputDateParser
- */
- RangeSelector.prototype.defaultInputDateParser = function (inputDate, useUTC, time) {
- var hasTimezone = function (str) {
- return str.length > 6 &&
- (str.lastIndexOf('-') === str.length - 6 ||
- str.lastIndexOf('+') === str.length - 6);
- };
- var input = inputDate.split('/').join('-').split(' ').join('T');
- if (input.indexOf('T') === -1) {
- input += 'T00:00';
- }
- if (useUTC) {
- input += 'Z';
- }
- else if (H.isSafari && !hasTimezone(input)) {
- var offset = new Date(input).getTimezoneOffset() / 60;
- input += offset <= 0 ? "+" + pad(-offset) + ":00" : "-" + pad(offset) + ":00";
- }
- var date = Date.parse(input);
- // If the value isn't parsed directly to a value by the
- // browser's Date.parse method, like YYYY-MM-DD in IE8, try
- // parsing it a different way
- if (!isNumber(date)) {
- var parts = inputDate.split('-');
- date = Date.UTC(pInt(parts[0]), pInt(parts[1]) - 1, pInt(parts[2]));
- }
- if (time && useUTC && isNumber(date)) {
- date += time.getTimezoneOffset(date);
- }
- return date;
- };
- /**
- * Draw either the 'from' or the 'to' HTML input box of the range selector
- *
- * @private
- * @function Highcharts.RangeSelector#drawInput
- * @param {string} name
- * @return {RangeSelectorInputElements}
- */
- RangeSelector.prototype.drawInput = function (name) {
- var _a = this,
- chart = _a.chart,
- div = _a.div,
- inputGroup = _a.inputGroup;
- var rangeSelector = this,
- chartStyle = chart.renderer.style || {},
- renderer = chart.renderer,
- options = chart.options.rangeSelector,
- lang = defaultOptions.lang,
- isMin = name === 'min';
- /**
- * @private
- */
- function updateExtremes() {
- var value = rangeSelector.getInputValue(name),
- chartAxis = chart.xAxis[0],
- dataAxis = chart.scroller && chart.scroller.xAxis ?
- chart.scroller.xAxis :
- chartAxis,
- dataMin = dataAxis.dataMin,
- dataMax = dataAxis.dataMax;
- var maxInput = rangeSelector.maxInput,
- minInput = rangeSelector.minInput;
- if (value !== Number(input.getAttribute('data-hc-time-previous')) &&
- isNumber(value)) {
- input.setAttribute('data-hc-time-previous', value);
- // Validate the extremes. If it goes beyound the data min or
- // max, use the actual data extreme (#2438).
- if (isMin && maxInput && isNumber(dataMin)) {
- if (value > Number(maxInput.getAttribute('data-hc-time'))) {
- value = void 0;
- }
- else if (value < dataMin) {
- value = dataMin;
- }
- }
- else if (minInput && isNumber(dataMax)) {
- if (value < Number(minInput.getAttribute('data-hc-time'))) {
- value = void 0;
- }
- else if (value > dataMax) {
- value = dataMax;
- }
- }
- // Set the extremes
- if (typeof value !== 'undefined') { // @todo typof undefined
- chartAxis.setExtremes(isMin ? value : chartAxis.min, isMin ? chartAxis.max : value, void 0, void 0, { trigger: 'rangeSelectorInput' });
- }
- }
- }
- // Create the text label
- var text = lang[isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo'];
- var label = renderer
- .label(text, 0)
- .addClass('highcharts-range-label')
- .attr({
- padding: text ? 2 : 0,
- height: text ? options.inputBoxHeight : 0
- })
- .add(inputGroup);
- // Create an SVG label that shows updated date ranges and and records
- // click events that bring in the HTML input.
- var dateBox = renderer
- .label('', 0)
- .addClass('highcharts-range-input')
- .attr({
- padding: 2,
- width: options.inputBoxWidth,
- height: options.inputBoxHeight,
- 'text-align': 'center'
- })
- .on('click',
- function () {
- // If it is already focused, the onfocus event doesn't fire
- // (#3713)
- rangeSelector.showInput(name);
- rangeSelector[name + 'Input'].focus();
- });
- if (!chart.styledMode) {
- dateBox.attr({
- stroke: options.inputBoxBorderColor,
- 'stroke-width': 1
- });
- }
- dateBox.add(inputGroup);
- // Create the HTML input element. This is rendered as 1x1 pixel then set
- // to the right size when focused.
- var input = createElement('input', {
- name: name,
- className: 'highcharts-range-selector'
- },
- void 0,
- div);
- // #14788: Setting input.type to an unsupported type throws in IE, so
- // we need to use setAttribute instead
- input.setAttribute('type', preferredInputType(options.inputDateFormat || '%b %e, %Y'));
- if (!chart.styledMode) {
- // Styles
- label.css(merge(chartStyle, options.labelStyle));
- dateBox.css(merge({
- color: palette.neutralColor80
- }, chartStyle, options.inputStyle));
- css(input, extend({
- position: 'absolute',
- border: 0,
- boxShadow: '0 0 15px rgba(0,0,0,0.3)',
- width: '1px',
- height: '1px',
- padding: 0,
- textAlign: 'center',
- fontSize: chartStyle.fontSize,
- fontFamily: chartStyle.fontFamily,
- top: '-9999em' // #4798
- }, options.inputStyle));
- }
- // Blow up the input box
- input.onfocus = function () {
- rangeSelector.showInput(name);
- };
- // Hide away the input box
- input.onblur = function () {
- // update extermes only when inputs are active
- if (input === H.doc.activeElement) { // Only when focused
- // Update also when no `change` event is triggered, like when
- // clicking inside the SVG (#4710)
- updateExtremes();
- }
- // #10404 - move hide and blur outside focus
- rangeSelector.hideInput(name);
- rangeSelector.setInputValue(name);
- input.blur(); // #4606
- };
- var keyDown = false;
- // handle changes in the input boxes
- input.onchange = function () {
- // Update extremes and blur input when clicking date input calendar
- if (!keyDown) {
- updateExtremes();
- rangeSelector.hideInput(name);
- input.blur();
- }
- };
- input.onkeypress = function (event) {
- // IE does not fire onchange on enter
- if (event.keyCode === 13) {
- updateExtremes();
- }
- };
- input.onkeydown = function (event) {
- keyDown = true;
- // Arrow keys
- if (event.keyCode === 38 || event.keyCode === 40) {
- updateExtremes();
- }
- };
- input.onkeyup = function () {
- keyDown = false;
- };
- return { dateBox: dateBox, input: input, label: label };
- };
- /**
- * Get the position of the range selector buttons and inputs. This can be
- * overridden from outside for custom positioning.
- *
- * @private
- * @function Highcharts.RangeSelector#getPosition
- *
- * @return {Highcharts.Dictionary<number>}
- */
- RangeSelector.prototype.getPosition = function () {
- var chart = this.chart,
- options = chart.options.rangeSelector,
- top = options.verticalAlign === 'top' ?
- chart.plotTop - chart.axisOffset[0] :
- 0; // set offset only for varticalAlign top
- return {
- buttonTop: top + options.buttonPosition.y,
- inputTop: top + options.inputPosition.y - 10
- };
- };
- /**
- * Get the extremes of YTD. Will choose dataMax if its value is lower than
- * the current timestamp. Will choose dataMin if its value is higher than
- * the timestamp for the start of current year.
- *
- * @private
- * @function Highcharts.RangeSelector#getYTDExtremes
- *
- * @param {number} dataMax
- *
- * @param {number} dataMin
- *
- * @return {*}
- * Returns min and max for the YTD
- */
- RangeSelector.prototype.getYTDExtremes = function (dataMax, dataMin, useUTC) {
- var time = this.chart.time,
- min,
- now = new time.Date(dataMax),
- year = time.get('FullYear',
- now),
- startOfYear = useUTC ?
- time.Date.UTC(year, 0, 1) : // eslint-disable-line new-cap
- +new time.Date(year, 0, 1);
- min = Math.max(dataMin, startOfYear);
- var ts = now.getTime();
- return {
- max: Math.min(dataMax || ts, ts),
- min: min
- };
- };
- /**
- * Render the range selector including the buttons and the inputs. The first
- * time render is called, the elements are created and positioned. On
- * subsequent calls, they are moved and updated.
- *
- * @private
- * @function Highcharts.RangeSelector#render
- * @param {number} [min]
- * X axis minimum
- * @param {number} [max]
- * X axis maximum
- * @return {void}
- */
- RangeSelector.prototype.render = function (min, max) {
- var chart = this.chart,
- renderer = chart.renderer,
- container = chart.container,
- chartOptions = chart.options,
- options = chartOptions.rangeSelector,
- // Place inputs above the container
- inputsZIndex = pick(chartOptions.chart.style &&
- chartOptions.chart.style.zIndex, 0) + 1,
- inputEnabled = options.inputEnabled,
- rendered = this.rendered;
- if (options.enabled === false) {
- return;
- }
- // create the elements
- if (!rendered) {
- this.group = renderer.g('range-selector-group')
- .attr({
- zIndex: 7
- })
- .add();
- this.div = createElement('div', void 0, {
- position: 'relative',
- height: 0,
- zIndex: inputsZIndex
- });
- if (this.buttonOptions.length) {
- this.renderButtons();
- }
- // First create a wrapper outside the container in order to make
- // the inputs work and make export correct
- if (container.parentNode) {
- container.parentNode.insertBefore(this.div, container);
- }
- if (inputEnabled) {
- // Create the group to keep the inputs
- this.inputGroup = renderer.g('input-group').add(this.group);
- var minElems = this.drawInput('min');
- this.minDateBox = minElems.dateBox;
- this.minLabel = minElems.label;
- this.minInput = minElems.input;
- var maxElems = this.drawInput('max');
- this.maxDateBox = maxElems.dateBox;
- this.maxLabel = maxElems.label;
- this.maxInput = maxElems.input;
- }
- }
- if (inputEnabled) {
- // Set or reset the input values
- this.setInputValue('min', min);
- this.setInputValue('max', max);
- var unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || chart.xAxis[0] || {};
- if (defined(unionExtremes.dataMin) && defined(unionExtremes.dataMax)) {
- var minRange = chart.xAxis[0].minRange || 0;
- this.setInputExtremes('min', unionExtremes.dataMin, Math.min(unionExtremes.dataMax, this.getInputValue('max')) - minRange);
- this.setInputExtremes('max', Math.max(unionExtremes.dataMin, this.getInputValue('min')) + minRange, unionExtremes.dataMax);
- }
- // Reflow
- if (this.inputGroup) {
- var x_1 = 0;
- [
- this.minLabel,
- this.minDateBox,
- this.maxLabel,
- this.maxDateBox
- ].forEach(function (label) {
- if (label) {
- var width = label.getBBox().width;
- if (width) {
- label.attr({ x: x_1 });
- x_1 += width + options.inputSpacing;
- }
- }
- });
- }
- }
- this.alignElements();
- this.rendered = true;
- };
- /**
- * Render the range buttons. This only runs the first time, later the
- * positioning is laid out in alignElements.
- *
- * @private
- * @function Highcharts.RangeSelector#renderButtons
- * @return {void}
- */
- RangeSelector.prototype.renderButtons = function () {
- var _this = this;
- var _a = this,
- buttons = _a.buttons,
- chart = _a.chart,
- options = _a.options;
- var lang = defaultOptions.lang;
- var renderer = chart.renderer;
- var buttonTheme = merge(options.buttonTheme);
- var states = buttonTheme && buttonTheme.states;
- // Prevent the button from resetting the width when the button state
- // changes since we need more control over the width when collapsing
- // the buttons
- var width = buttonTheme.width || 28;
- delete buttonTheme.width;
- delete buttonTheme.states;
- this.buttonGroup = renderer.g('range-selector-buttons').add(this.group);
- var dropdown = this.dropdown = createElement('select',
- void 0, {
- position: 'absolute',
- width: '1px',
- height: '1px',
- padding: 0,
- border: 0,
- top: '-9999em',
- cursor: 'pointer',
- opacity: 0.0001
- },
- this.div);
- // Prevent page zoom on iPhone
- addEvent(dropdown, 'touchstart', function () {
- dropdown.style.fontSize = '16px';
- });
- // Forward events from select to button
- [
- [H.isMS ? 'mouseover' : 'mouseenter'],
- [H.isMS ? 'mouseout' : 'mouseleave'],
- ['change', 'click']
- ].forEach(function (_a) {
- var from = _a[0],
- to = _a[1];
- addEvent(dropdown, from, function () {
- var button = buttons[_this.currentButtonIndex()];
- if (button) {
- fireEvent(button.element, to || from);
- }
- });
- });
- this.zoomText = renderer
- .label((lang && lang.rangeSelectorZoom) || '', 0)
- .attr({
- padding: options.buttonTheme.padding,
- height: options.buttonTheme.height,
- paddingLeft: 0,
- paddingRight: 0
- })
- .add(this.buttonGroup);
- if (!this.chart.styledMode) {
- this.zoomText.css(options.labelStyle);
- buttonTheme['stroke-width'] = pick(buttonTheme['stroke-width'], 0);
- }
- createElement('option', {
- textContent: this.zoomText.textStr,
- disabled: true
- }, void 0, dropdown);
- this.buttonOptions.forEach(function (rangeOptions, i) {
- createElement('option', {
- textContent: rangeOptions.title || rangeOptions.text
- }, void 0, dropdown);
- buttons[i] = renderer
- .button(rangeOptions.text, 0, 0, function (e) {
- // extract events from button object and call
- var buttonEvents = (rangeOptions.events &&
- rangeOptions.events.click),
- callDefaultEvent;
- if (buttonEvents) {
- callDefaultEvent =
- buttonEvents.call(rangeOptions, e);
- }
- if (callDefaultEvent !== false) {
- _this.clickButton(i);
- }
- _this.isActive = true;
- }, buttonTheme, states && states.hover, states && states.select, states && states.disabled)
- .attr({
- 'text-align': 'center',
- width: width
- })
- .add(_this.buttonGroup);
- if (rangeOptions.title) {
- buttons[i].attr('title', rangeOptions.title);
- }
- });
- };
- /**
- * Align the elements horizontally and vertically.
- *
- * @private
- * @function Highcharts.RangeSelector#alignElements
- * @return {void}
- */
- RangeSelector.prototype.alignElements = function () {
- var _this = this;
- var _a = this,
- buttonGroup = _a.buttonGroup,
- buttons = _a.buttons,
- chart = _a.chart,
- group = _a.group,
- inputGroup = _a.inputGroup,
- options = _a.options,
- zoomText = _a.zoomText;
- var chartOptions = chart.options;
- var navButtonOptions = (chartOptions.exporting &&
- chartOptions.exporting.enabled !== false &&
- chartOptions.navigation &&
- chartOptions.navigation.buttonOptions);
- var buttonPosition = options.buttonPosition,
- inputPosition = options.inputPosition,
- verticalAlign = options.verticalAlign;
- // Get the X offset required to avoid overlapping with the exporting
- // button. This is is used both by the buttonGroup and the inputGroup.
- var getXOffsetForExportButton = function (group,
- position) {
- if (navButtonOptions &&
- _this.titleCollision(chart) &&
- verticalAlign === 'top' &&
- position.align === 'right' && ((position.y -
- group.getBBox().height - 12) <
- ((navButtonOptions.y || 0) +
- (navButtonOptions.height || 0) +
- chart.spacing[0]))) {
- return -40;
- }
- return 0;
- };
- var plotLeft = chart.plotLeft;
- if (group && buttonPosition && inputPosition) {
- var translateX = buttonPosition.x - chart.spacing[3];
- if (buttonGroup) {
- this.positionButtons();
- if (!this.initialButtonGroupWidth) {
- var width_1 = 0;
- if (zoomText) {
- width_1 += zoomText.getBBox().width + 5;
- }
- buttons.forEach(function (button, i) {
- width_1 += button.width;
- if (i !== buttons.length - 1) {
- width_1 += options.buttonSpacing;
- }
- });
- this.initialButtonGroupWidth = width_1;
- }
- plotLeft -= chart.spacing[3];
- this.updateButtonStates();
- // Detect collision between button group and exporting
- var xOffsetForExportButton_1 = getXOffsetForExportButton(buttonGroup,
- buttonPosition);
- this.alignButtonGroup(xOffsetForExportButton_1);
- // Skip animation
- group.placed = buttonGroup.placed = chart.hasLoaded;
- }
- var xOffsetForExportButton = 0;
- if (inputGroup) {
- // Detect collision between the input group and exporting button
- xOffsetForExportButton = getXOffsetForExportButton(inputGroup, inputPosition);
- if (inputPosition.align === 'left') {
- translateX = plotLeft;
- }
- else if (inputPosition.align === 'right') {
- translateX = -Math.max(chart.axisOffset[1], -xOffsetForExportButton);
- }
- // Update the alignment to the updated spacing box
- inputGroup.align({
- y: inputPosition.y,
- width: inputGroup.getBBox().width,
- align: inputPosition.align,
- // fix wrong getBBox() value on right align
- x: inputPosition.x + translateX - 2
- }, true, chart.spacingBox);
- // Skip animation
- inputGroup.placed = chart.hasLoaded;
- }
- this.handleCollision(xOffsetForExportButton);
- // Vertical align
- group.align({
- verticalAlign: verticalAlign
- }, true, chart.spacingBox);
- var alignTranslateY = group.alignAttr.translateY;
- // Set position
- var groupHeight = group.getBBox().height + 20; // # 20 padding
- var translateY = 0;
- // Calculate bottom position
- if (verticalAlign === 'bottom') {
- var legendOptions = chart.legend && chart.legend.options;
- var legendHeight = (legendOptions &&
- legendOptions.verticalAlign === 'bottom' &&
- legendOptions.enabled &&
- !legendOptions.floating ?
- (chart.legend.legendHeight +
- pick(legendOptions.margin, 10)) :
- 0);
- groupHeight = groupHeight + legendHeight - 20;
- translateY = (alignTranslateY -
- groupHeight -
- (options.floating ? 0 : options.y) -
- (chart.titleOffset ? chart.titleOffset[2] : 0) -
- 10 // 10 spacing
- );
- }
- if (verticalAlign === 'top') {
- if (options.floating) {
- translateY = 0;
- }
- if (chart.titleOffset && chart.titleOffset[0]) {
- translateY = chart.titleOffset[0];
- }
- translateY += ((chart.margin[0] - chart.spacing[0]) || 0);
- }
- else if (verticalAlign === 'middle') {
- if (inputPosition.y === buttonPosition.y) {
- translateY = alignTranslateY;
- }
- else if (inputPosition.y || buttonPosition.y) {
- if (inputPosition.y < 0 ||
- buttonPosition.y < 0) {
- translateY -= Math.min(inputPosition.y, buttonPosition.y);
- }
- else {
- translateY = alignTranslateY - groupHeight;
- }
- }
- }
- group.translate(options.x, options.y + Math.floor(translateY));
- // Translate HTML inputs
- var _b = this,
- minInput = _b.minInput,
- maxInput = _b.maxInput,
- dropdown = _b.dropdown;
- if (options.inputEnabled && minInput && maxInput) {
- minInput.style.marginTop = group.translateY + 'px';
- maxInput.style.marginTop = group.translateY + 'px';
- }
- if (dropdown) {
- dropdown.style.marginTop = group.translateY + 'px';
- }
- }
- };
- /**
- * Align the button group horizontally and vertically.
- *
- * @private
- * @function Highcharts.RangeSelector#alignButtonGroup
- * @param {number} xOffsetForExportButton
- * @param {number} [width]
- * @return {void}
- */
- RangeSelector.prototype.alignButtonGroup = function (xOffsetForExportButton, width) {
- var _a = this,
- chart = _a.chart,
- options = _a.options,
- buttonGroup = _a.buttonGroup,
- buttons = _a.buttons;
- var buttonPosition = options.buttonPosition;
- var plotLeft = chart.plotLeft - chart.spacing[3];
- var translateX = buttonPosition.x - chart.spacing[3];
- if (buttonPosition.align === 'right') {
- translateX += xOffsetForExportButton - plotLeft; // #13014
- }
- else if (buttonPosition.align === 'center') {
- translateX -= plotLeft / 2;
- }
- if (buttonGroup) {
- // Align button group
- buttonGroup.align({
- y: buttonPosition.y,
- width: pick(width, this.initialButtonGroupWidth),
- align: buttonPosition.align,
- x: translateX
- }, true, chart.spacingBox);
- }
- };
- /**
- * @private
- * @function Highcharts.RangeSelector#positionButtons
- * @return {void}
- */
- RangeSelector.prototype.positionButtons = function () {
- var _a = this,
- buttons = _a.buttons,
- chart = _a.chart,
- options = _a.options,
- zoomText = _a.zoomText;
- var verb = chart.hasLoaded ? 'animate' : 'attr';
- var buttonPosition = options.buttonPosition;
- var plotLeft = chart.plotLeft;
- var buttonLeft = plotLeft;
- if (zoomText && zoomText.visibility !== 'hidden') {
- // #8769, allow dynamically updating margins
- zoomText[verb]({
- x: pick(plotLeft + buttonPosition.x, plotLeft)
- });
- // Button start position
- buttonLeft += buttonPosition.x +
- zoomText.getBBox().width + 5;
- }
- this.buttonOptions.forEach(function (rangeOptions, i) {
- if (buttons[i].visibility !== 'hidden') {
- buttons[i][verb]({ x: buttonLeft });
- // increase button position for the next button
- buttonLeft += buttons[i].width + options.buttonSpacing;
- }
- else {
- buttons[i][verb]({ x: plotLeft });
- }
- });
- };
- /**
- * Handle collision between the button group and the input group
- *
- * @private
- * @function Highcharts.RangeSelector#handleCollision
- *
- * @param {number} xOffsetForExportButton
- * The X offset of the group required to make room for the
- * exporting button
- * @return {void}
- */
- RangeSelector.prototype.handleCollision = function (xOffsetForExportButton) {
- var _this = this;
- var _a = this,
- chart = _a.chart,
- buttonGroup = _a.buttonGroup,
- inputGroup = _a.inputGroup;
- var _b = this.options,
- buttonPosition = _b.buttonPosition,
- dropdown = _b.dropdown,
- inputPosition = _b.inputPosition;
- var maxButtonWidth = function () {
- var buttonWidth = 0;
- _this.buttons.forEach(function (button) {
- var bBox = button.getBBox();
- if (bBox.width > buttonWidth) {
- buttonWidth = bBox.width;
- }
- });
- return buttonWidth;
- };
- var groupsOverlap = function (buttonGroupWidth) {
- if (inputGroup && buttonGroup) {
- var inputGroupX = (inputGroup.alignAttr.translateX +
- inputGroup.alignOptions.x -
- xOffsetForExportButton +
- // getBBox for detecing left margin
- inputGroup.getBBox().x +
- // 2px padding to not overlap input and label
- 2);
- var inputGroupWidth = inputGroup.alignOptions.width;
- var buttonGroupX = buttonGroup.alignAttr.translateX +
- buttonGroup.getBBox().x;
- return (buttonGroupX + buttonGroupWidth > inputGroupX) &&
- (inputGroupX + inputGroupWidth > buttonGroupX) &&
- (buttonPosition.y <
- (inputPosition.y +
- inputGroup.getBBox().height));
- }
- return false;
- };
- var moveInputsDown = function () {
- if (inputGroup && buttonGroup) {
- inputGroup.attr({
- translateX: inputGroup.alignAttr.translateX + (chart.axisOffset[1] >= -xOffsetForExportButton ?
- 0 :
- -xOffsetForExportButton),
- translateY: inputGroup.alignAttr.translateY +
- buttonGroup.getBBox().height + 10
- });
- }
- };
- if (buttonGroup) {
- if (dropdown === 'always') {
- this.collapseButtons(xOffsetForExportButton);
- if (groupsOverlap(maxButtonWidth())) {
- // Move the inputs down if there is still a collision
- // after collapsing the buttons
- moveInputsDown();
- }
- return;
- }
- if (dropdown === 'never') {
- this.expandButtons();
- }
- }
- // Detect collision
- if (inputGroup && buttonGroup) {
- if ((inputPosition.align === buttonPosition.align) ||
- // 20 is minimal spacing between elements
- groupsOverlap(this.initialButtonGroupWidth + 20)) {
- if (dropdown === 'responsive') {
- this.collapseButtons(xOffsetForExportButton);
- if (groupsOverlap(maxButtonWidth())) {
- moveInputsDown();
- }
- }
- else {
- moveInputsDown();
- }
- }
- else if (dropdown === 'responsive') {
- this.expandButtons();
- }
- }
- else if (buttonGroup && dropdown === 'responsive') {
- if (this.initialButtonGroupWidth > chart.plotWidth) {
- this.collapseButtons(xOffsetForExportButton);
- }
- else {
- this.expandButtons();
- }
- }
- };
- /**
- * Collapse the buttons and put the select element on top.
- *
- * @private
- * @function Highcharts.RangeSelector#collapseButtons
- * @param {number} xOffsetForExportButton
- * @return {void}
- */
- RangeSelector.prototype.collapseButtons = function (xOffsetForExportButton) {
- var _a = this,
- buttons = _a.buttons,
- buttonOptions = _a.buttonOptions,
- chart = _a.chart,
- dropdown = _a.dropdown,
- options = _a.options,
- zoomText = _a.zoomText;
- var userButtonTheme = (chart.userOptions.rangeSelector &&
- chart.userOptions.rangeSelector.buttonTheme) || {};
- var getAttribs = function (text) { return ({
- text: text ? text + " \u25BE" : '▾',
- width: 'auto',
- paddingLeft: pick(options.buttonTheme.paddingLeft,
- userButtonTheme.padding, 8),
- paddingRight: pick(options.buttonTheme.paddingRight,
- userButtonTheme.padding, 8)
- }); };
- if (zoomText) {
- zoomText.hide();
- }
- var hasActiveButton = false;
- buttonOptions.forEach(function (rangeOptions, i) {
- var button = buttons[i];
- if (button.state !== 2) {
- button.hide();
- }
- else {
- button.show();
- button.attr(getAttribs(rangeOptions.text));
- hasActiveButton = true;
- }
- });
- if (!hasActiveButton) {
- if (dropdown) {
- dropdown.selectedIndex = 0;
- }
- buttons[0].show();
- buttons[0].attr(getAttribs(this.zoomText && this.zoomText.textStr));
- }
- var align = options.buttonPosition.align;
- this.positionButtons();
- if (align === 'right' || align === 'center') {
- this.alignButtonGroup(xOffsetForExportButton, buttons[this.currentButtonIndex()].getBBox().width);
- }
- this.showDropdown();
- };
- /**
- * Show all the buttons and hide the select element.
- *
- * @private
- * @function Highcharts.RangeSelector#expandButtons
- * @return {void}
- */
- RangeSelector.prototype.expandButtons = function () {
- var _a = this,
- buttons = _a.buttons,
- buttonOptions = _a.buttonOptions,
- options = _a.options,
- zoomText = _a.zoomText;
- this.hideDropdown();
- if (zoomText) {
- zoomText.show();
- }
- buttonOptions.forEach(function (rangeOptions, i) {
- var button = buttons[i];
- button.show();
- button.attr({
- text: rangeOptions.text,
- width: options.buttonTheme.width || 28,
- paddingLeft: pick(options.buttonTheme.paddingLeft, 'unset'),
- paddingRight: pick(options.buttonTheme.paddingRight, 'unset')
- });
- if (button.state < 2) {
- button.setState(0);
- }
- });
- this.positionButtons();
- };
- /**
- * Get the index of the visible button when the buttons are collapsed.
- *
- * @private
- * @function Highcharts.RangeSelector#currentButtonIndex
- * @return {number}
- */
- RangeSelector.prototype.currentButtonIndex = function () {
- var dropdown = this.dropdown;
- if (dropdown && dropdown.selectedIndex > 0) {
- return dropdown.selectedIndex - 1;
- }
- return 0;
- };
- /**
- * Position the select element on top of the button.
- *
- * @private
- * @function Highcharts.RangeSelector#showDropdown
- * @return {void}
- */
- RangeSelector.prototype.showDropdown = function () {
- var _a = this,
- buttonGroup = _a.buttonGroup,
- buttons = _a.buttons,
- chart = _a.chart,
- dropdown = _a.dropdown;
- if (buttonGroup && dropdown) {
- var translateX = buttonGroup.translateX,
- translateY = buttonGroup.translateY;
- var bBox = buttons[this.currentButtonIndex()].getBBox();
- css(dropdown, {
- left: (chart.plotLeft + translateX) + 'px',
- top: (translateY + 0.5) + 'px',
- width: bBox.width + 'px',
- height: bBox.height + 'px'
- });
- this.hasVisibleDropdown = true;
- }
- };
- /**
- * @private
- * @function Highcharts.RangeSelector#hideDropdown
- * @return {void}
- */
- RangeSelector.prototype.hideDropdown = function () {
- var dropdown = this.dropdown;
- if (dropdown) {
- css(dropdown, {
- top: '-9999em',
- width: '1px',
- height: '1px'
- });
- this.hasVisibleDropdown = false;
- }
- };
- /**
- * Extracts height of range selector
- *
- * @private
- * @function Highcharts.RangeSelector#getHeight
- * @return {number}
- * Returns rangeSelector height
- */
- RangeSelector.prototype.getHeight = function () {
- var rangeSelector = this,
- options = rangeSelector.options,
- rangeSelectorGroup = rangeSelector.group,
- inputPosition = options.inputPosition,
- buttonPosition = options.buttonPosition,
- yPosition = options.y,
- buttonPositionY = buttonPosition.y,
- inputPositionY = inputPosition.y,
- rangeSelectorHeight = 0,
- minPosition;
- if (options.height) {
- return options.height;
- }
- // Align the elements before we read the height in case we're switching
- // between wrapped and non-wrapped layout
- this.alignElements();
- rangeSelectorHeight = rangeSelectorGroup ?
- // 13px to keep back compatibility
- (rangeSelectorGroup.getBBox(true).height) + 13 +
- yPosition :
- 0;
- minPosition = Math.min(inputPositionY, buttonPositionY);
- if ((inputPositionY < 0 && buttonPositionY < 0) ||
- (inputPositionY > 0 && buttonPositionY > 0)) {
- rangeSelectorHeight += Math.abs(minPosition);
- }
- return rangeSelectorHeight;
- };
- /**
- * Detect collision with title or subtitle
- *
- * @private
- * @function Highcharts.RangeSelector#titleCollision
- *
- * @param {Highcharts.Chart} chart
- *
- * @return {boolean}
- * Returns collision status
- */
- RangeSelector.prototype.titleCollision = function (chart) {
- return !(chart.options.title.text ||
- chart.options.subtitle.text);
- };
- /**
- * Update the range selector with new options
- *
- * @private
- * @function Highcharts.RangeSelector#update
- * @param {Highcharts.RangeSelectorOptions} options
- * @return {void}
- */
- RangeSelector.prototype.update = function (options) {
- var chart = this.chart;
- merge(true, chart.options.rangeSelector, options);
- this.destroy();
- this.init(chart);
- this.render();
- };
- /**
- * Destroys allocated elements.
- *
- * @private
- * @function Highcharts.RangeSelector#destroy
- */
- RangeSelector.prototype.destroy = function () {
- var rSelector = this,
- minInput = rSelector.minInput,
- maxInput = rSelector.maxInput;
- if (rSelector.eventsToUnbind) {
- rSelector.eventsToUnbind.forEach(function (unbind) { return unbind(); });
- rSelector.eventsToUnbind = void 0;
- }
- // Destroy elements in collections
- destroyObjectProperties(rSelector.buttons);
- // Clear input element events
- if (minInput) {
- minInput.onfocus = minInput.onblur = minInput.onchange = null;
- }
- if (maxInput) {
- maxInput.onfocus = maxInput.onblur = maxInput.onchange = null;
- }
- // Destroy HTML and SVG elements
- objectEach(rSelector, function (val, key) {
- if (val && key !== 'chart') {
- if (val instanceof SVGElement) {
- // SVGElement
- val.destroy();
- }
- else if (val instanceof window.HTMLElement) {
- // HTML element
- discardElement(val);
- }
- }
- if (val !== RangeSelector.prototype[key]) {
- rSelector[key] = null;
- }
- }, this);
- };
- return RangeSelector;
- }());
- /**
- * The default buttons for pre-selecting time frames
- */
- RangeSelector.prototype.defaultButtons = [{
- type: 'month',
- count: 1,
- text: '1m',
- title: 'View 1 month'
- }, {
- type: 'month',
- count: 3,
- text: '3m',
- title: 'View 3 months'
- }, {
- type: 'month',
- count: 6,
- text: '6m',
- title: 'View 6 months'
- }, {
- type: 'ytd',
- text: 'YTD',
- title: 'View year to date'
- }, {
- type: 'year',
- count: 1,
- text: '1y',
- title: 'View 1 year'
- }, {
- type: 'all',
- text: 'All',
- title: 'View all'
- }];
- /**
- * The date formats to use when setting min, max and value on date inputs
- */
- RangeSelector.prototype.inputTypeFormats = {
- 'datetime-local': '%Y-%m-%dT%H:%M:%S',
- 'date': '%Y-%m-%d',
- 'time': '%H:%M:%S'
- };
- /**
- * Get the preferred input type based on a date format string.
- *
- * @private
- * @function preferredInputType
- * @param {string} format
- * @return {string}
- */
- function preferredInputType(format) {
- var ms = format.indexOf('%L') !== -1;
- if (ms) {
- return 'text';
- }
- var date = ['a', 'A', 'd', 'e', 'w', 'b', 'B', 'm', 'o', 'y', 'Y'].some(function (char) {
- return format.indexOf('%' + char) !== -1;
- });
- var time = ['H', 'k', 'I', 'l', 'M', 'S'].some(function (char) {
- return format.indexOf('%' + char) !== -1;
- });
- if (date && time) {
- return 'datetime-local';
- }
- if (date) {
- return 'date';
- }
- if (time) {
- return 'time';
- }
- return 'text';
- }
- /**
- * Get the axis min value based on the range option and the current max. For
- * stock charts this is extended via the {@link RangeSelector} so that if the
- * selected range is a multiple of months or years, it is compensated for
- * various month lengths.
- *
- * @private
- * @function Highcharts.Axis#minFromRange
- * @return {number|undefined}
- * The new minimum value.
- */
- Axis.prototype.minFromRange = function () {
- var rangeOptions = this.range,
- type = rangeOptions.type,
- min,
- max = this.max,
- dataMin,
- range,
- time = this.chart.time,
- // Get the true range from a start date
- getTrueRange = function (base,
- count) {
- var timeName = type === 'year' ? 'FullYear' : 'Month';
- var date = new time.Date(base);
- var basePeriod = time.get(timeName,
- date);
- time.set(timeName, date, basePeriod + count);
- if (basePeriod === time.get(timeName, date)) {
- time.set('Date', date, 0); // #6537
- }
- return date.getTime() - base;
- };
- if (isNumber(rangeOptions)) {
- min = max - rangeOptions;
- range = rangeOptions;
- }
- else {
- min = max + getTrueRange(max, -rangeOptions.count);
- // Let the fixedRange reflect initial settings (#5930)
- if (this.chart) {
- this.chart.fixedRange = max - min;
- }
- }
- dataMin = pick(this.dataMin, Number.MIN_VALUE);
- if (!isNumber(min)) {
- min = dataMin;
- }
- if (min <= dataMin) {
- min = dataMin;
- if (typeof range === 'undefined') { // #4501
- range = getTrueRange(min, rangeOptions.count);
- }
- this.newMax = Math.min(min + range, this.dataMax);
- }
- if (!isNumber(max)) {
- min = void 0;
- }
- return min;
- };
- if (!H.RangeSelector) {
- var chartDestroyEvents_1 = [];
- var initRangeSelector_1 = function (chart) {
- var extremes,
- rangeSelector = chart.rangeSelector,
- legend,
- alignTo,
- verticalAlign;
- /**
- * @private
- */
- function render() {
- if (rangeSelector) {
- extremes = chart.xAxis[0].getExtremes();
- legend = chart.legend;
- verticalAlign = (rangeSelector &&
- rangeSelector.options.verticalAlign);
- if (isNumber(extremes.min)) {
- rangeSelector.render(extremes.min, extremes.max);
- }
- // Re-align the legend so that it's below the rangeselector
- if (legend.display &&
- verticalAlign === 'top' &&
- verticalAlign === legend.options.verticalAlign) {
- // Create a new alignment box for the legend.
- alignTo = merge(chart.spacingBox);
- if (legend.options.layout === 'vertical') {
- alignTo.y = chart.plotTop;
- }
- else {
- alignTo.y += rangeSelector.getHeight();
- }
- legend.group.placed = false; // Don't animate the alignment.
- legend.align(alignTo);
- }
- }
- }
- if (rangeSelector) {
- var events = find(chartDestroyEvents_1,
- function (e) { return e[0] === chart; });
- if (!events) {
- chartDestroyEvents_1.push([chart, [
- // redraw the scroller on setExtremes
- addEvent(chart.xAxis[0], 'afterSetExtremes', function (e) {
- if (rangeSelector) {
- rangeSelector.render(e.min, e.max);
- }
- }),
- // redraw the scroller chart resize
- addEvent(chart, 'redraw', render)
- ]]);
- }
- // do it now
- render();
- }
- };
- // Initialize rangeselector for stock charts
- addEvent(Chart, 'afterGetContainer', function () {
- if (this.options.rangeSelector &&
- this.options.rangeSelector.enabled) {
- this.rangeSelector = new RangeSelector(this);
- }
- });
- addEvent(Chart, 'beforeRender', function () {
- var chart = this,
- axes = chart.axes,
- rangeSelector = chart.rangeSelector,
- verticalAlign;
- if (rangeSelector) {
- if (isNumber(rangeSelector.deferredYTDClick)) {
- rangeSelector.clickButton(rangeSelector.deferredYTDClick);
- delete rangeSelector.deferredYTDClick;
- }
- axes.forEach(function (axis) {
- axis.updateNames();
- axis.setScale();
- });
- chart.getAxisMargins();
- rangeSelector.render();
- verticalAlign = rangeSelector.options.verticalAlign;
- if (!rangeSelector.options.floating) {
- if (verticalAlign === 'bottom') {
- this.extraBottomMargin = true;
- }
- else if (verticalAlign !== 'middle') {
- this.extraTopMargin = true;
- }
- }
- }
- });
- addEvent(Chart, 'update', function (e) {
- var chart = this,
- options = e.options,
- optionsRangeSelector = options.rangeSelector,
- rangeSelector = chart.rangeSelector,
- verticalAlign,
- extraBottomMarginWas = this.extraBottomMargin,
- extraTopMarginWas = this.extraTopMargin;
- if (optionsRangeSelector &&
- optionsRangeSelector.enabled &&
- !defined(rangeSelector) &&
- this.options.rangeSelector) {
- this.options.rangeSelector.enabled = true;
- this.rangeSelector = rangeSelector = new RangeSelector(this);
- }
- this.extraBottomMargin = false;
- this.extraTopMargin = false;
- if (rangeSelector) {
- initRangeSelector_1(this);
- verticalAlign = (optionsRangeSelector &&
- optionsRangeSelector.verticalAlign) || (rangeSelector.options && rangeSelector.options.verticalAlign);
- if (!rangeSelector.options.floating) {
- if (verticalAlign === 'bottom') {
- this.extraBottomMargin = true;
- }
- else if (verticalAlign !== 'middle') {
- this.extraTopMargin = true;
- }
- }
- if (this.extraBottomMargin !== extraBottomMarginWas ||
- this.extraTopMargin !== extraTopMarginWas) {
- this.isDirtyBox = true;
- }
- }
- });
- addEvent(Chart, 'render', function () {
- var chart = this,
- rangeSelector = chart.rangeSelector,
- verticalAlign;
- if (rangeSelector && !rangeSelector.options.floating) {
- rangeSelector.render();
- verticalAlign = rangeSelector.options.verticalAlign;
- if (verticalAlign === 'bottom') {
- this.extraBottomMargin = true;
- }
- else if (verticalAlign !== 'middle') {
- this.extraTopMargin = true;
- }
- }
- });
- addEvent(Chart, 'getMargins', function () {
- var rangeSelector = this.rangeSelector,
- rangeSelectorHeight;
- if (rangeSelector) {
- rangeSelectorHeight = rangeSelector.getHeight();
- if (this.extraTopMargin) {
- this.plotTop += rangeSelectorHeight;
- }
- if (this.extraBottomMargin) {
- this.marginBottom += rangeSelectorHeight;
- }
- }
- });
- Chart.prototype.callbacks.push(initRangeSelector_1);
- // Remove resize/afterSetExtremes at chart destroy
- addEvent(Chart, 'destroy', function destroyEvents() {
- for (var i = 0; i < chartDestroyEvents_1.length; i++) {
- var events = chartDestroyEvents_1[i];
- if (events[0] === this) {
- events[1].forEach(function (unbind) { return unbind(); });
- chartDestroyEvents_1.splice(i, 1);
- return;
- }
- }
- });
- H.RangeSelector = RangeSelector;
- }
- return RangeSelector;
- });
- _registerModule(_modules, 'Accessibility/Components/RangeSelectorComponent.js', [_modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/Announcer.js'], _modules['Core/Chart/Chart.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Core/Utilities.js'], _modules['Extensions/RangeSelector.js']], function (AccessibilityComponent, ChartUtilities, Announcer, Chart, HTMLUtilities, KeyboardNavigationHandler, U, RangeSelector) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Accessibility component for the range selector.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT,
- getAxisRangeDescription = ChartUtilities.getAxisRangeDescription;
- var setElAttrs = HTMLUtilities.setElAttrs;
- var addEvent = U.addEvent,
- extend = U.extend;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * @private
- */
- function shouldRunInputNavigation(chart) {
- return Boolean(chart.rangeSelector &&
- chart.rangeSelector.inputGroup &&
- chart.rangeSelector.inputGroup.element
- .getAttribute('visibility') !== 'hidden' &&
- chart.options.rangeSelector.inputEnabled !== false &&
- chart.rangeSelector.minInput &&
- chart.rangeSelector.maxInput);
- }
- /**
- * Highlight range selector button by index.
- *
- * @private
- * @function Highcharts.Chart#highlightRangeSelectorButton
- *
- * @param {number} ix
- *
- * @return {boolean}
- */
- Chart.prototype.highlightRangeSelectorButton = function (ix) {
- var buttons = (this.rangeSelector &&
- this.rangeSelector.buttons ||
- []);
- var curHighlightedIx = this.highlightedRangeSelectorItemIx;
- var curSelectedIx = (this.rangeSelector &&
- this.rangeSelector.selected);
- // Deselect old
- if (typeof curHighlightedIx !== 'undefined' &&
- buttons[curHighlightedIx] &&
- curHighlightedIx !== curSelectedIx) {
- buttons[curHighlightedIx].setState(this.oldRangeSelectorItemState || 0);
- }
- // Select new
- this.highlightedRangeSelectorItemIx = ix;
- if (buttons[ix]) {
- this.setFocusToElement(buttons[ix].box, buttons[ix].element);
- if (ix !== curSelectedIx) {
- this.oldRangeSelectorItemState = buttons[ix].state;
- buttons[ix].setState(1);
- }
- return true;
- }
- return false;
- };
- // Range selector does not have destroy-setup for class instance events - so
- // we set it on the class and call the component from here.
- addEvent(RangeSelector, 'afterBtnClick', function () {
- if (this.chart.accessibility &&
- this.chart.accessibility.components.rangeSelector) {
- return this.chart.accessibility.components.rangeSelector.onAfterBtnClick();
- }
- });
- /**
- * The RangeSelectorComponent class
- *
- * @private
- * @class
- * @name Highcharts.RangeSelectorComponent
- */
- var RangeSelectorComponent = function () { };
- RangeSelectorComponent.prototype = new AccessibilityComponent();
- extend(RangeSelectorComponent.prototype, /** @lends Highcharts.RangeSelectorComponent */ {
- /**
- * Init the component
- * @private
- */
- init: function () {
- var chart = this.chart;
- this.announcer = new Announcer(chart, 'polite');
- },
- /**
- * Called on first render/updates to the chart, including options changes.
- */
- onChartUpdate: function () {
- var chart = this.chart,
- component = this,
- rangeSelector = chart.rangeSelector;
- if (!rangeSelector) {
- return;
- }
- this.updateSelectorVisibility();
- this.setDropdownAttrs();
- if (rangeSelector.buttons &&
- rangeSelector.buttons.length) {
- rangeSelector.buttons.forEach(function (button) {
- component.setRangeButtonAttrs(button);
- });
- }
- // Make sure input boxes are accessible and focusable
- if (rangeSelector.maxInput && rangeSelector.minInput) {
- ['minInput', 'maxInput'].forEach(function (key, i) {
- var input = rangeSelector[key];
- if (input) {
- unhideChartElementFromAT(chart, input);
- component.setRangeInputAttrs(input, 'accessibility.rangeSelector.' + (i ? 'max' : 'min') +
- 'InputLabel');
- }
- });
- }
- },
- /**
- * Hide buttons from AT when showing dropdown, and vice versa.
- * @private
- */
- updateSelectorVisibility: function () {
- var chart = this.chart;
- var rangeSelector = chart.rangeSelector;
- var dropdown = (rangeSelector &&
- rangeSelector.dropdown);
- var buttons = (rangeSelector &&
- rangeSelector.buttons ||
- []);
- var hideFromAT = function (el) { return el.setAttribute('aria-hidden',
- true); };
- if (rangeSelector &&
- rangeSelector.hasVisibleDropdown &&
- dropdown) {
- unhideChartElementFromAT(chart, dropdown);
- buttons.forEach(function (btn) { return hideFromAT(btn.element); });
- }
- else {
- if (dropdown) {
- hideFromAT(dropdown);
- }
- buttons.forEach(function (btn) { return unhideChartElementFromAT(chart, btn.element); });
- }
- },
- /**
- * Set accessibility related attributes on dropdown element.
- * @private
- */
- setDropdownAttrs: function () {
- var chart = this.chart;
- var dropdown = (chart.rangeSelector &&
- chart.rangeSelector.dropdown);
- if (dropdown) {
- var label = chart.langFormat('accessibility.rangeSelector.dropdownLabel', { rangeTitle: chart.options.lang.rangeSelectorZoom });
- dropdown.setAttribute('aria-label', label);
- dropdown.setAttribute('tabindex', -1);
- }
- },
- /**
- * @private
- * @param {Highcharts.SVGElement} button
- */
- setRangeButtonAttrs: function (button) {
- setElAttrs(button.element, {
- tabindex: -1,
- role: 'button'
- });
- },
- /**
- * @private
- */
- setRangeInputAttrs: function (input, langKey) {
- var chart = this.chart;
- setElAttrs(input, {
- tabindex: -1,
- 'aria-label': chart.langFormat(langKey, { chart: chart })
- });
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @param {number} keyCode
- * @return {number} Response code
- */
- onButtonNavKbdArrowKey: function (keyboardNavigationHandler, keyCode) {
- var response = keyboardNavigationHandler.response,
- keys = this.keyCodes,
- chart = this.chart,
- wrapAround = chart.options.accessibility
- .keyboardNavigation.wrapAround,
- direction = (keyCode === keys.left || keyCode === keys.up) ? -1 : 1,
- didHighlight = chart.highlightRangeSelectorButton(chart.highlightedRangeSelectorItemIx + direction);
- if (!didHighlight) {
- if (wrapAround) {
- keyboardNavigationHandler.init(direction);
- return response.success;
- }
- return response[direction > 0 ? 'next' : 'prev'];
- }
- return response.success;
- },
- /**
- * @private
- */
- onButtonNavKbdClick: function (keyboardNavigationHandler) {
- var response = keyboardNavigationHandler.response,
- chart = this.chart,
- wasDisabled = chart.oldRangeSelectorItemState === 3;
- if (!wasDisabled) {
- this.fakeClickEvent(chart.rangeSelector.buttons[chart.highlightedRangeSelectorItemIx].element);
- }
- return response.success;
- },
- /**
- * Called whenever a range selector button has been clicked, either by
- * mouse, touch, or kbd/voice/other.
- * @private
- */
- onAfterBtnClick: function () {
- var chart = this.chart;
- var axisRangeDescription = getAxisRangeDescription(chart.xAxis[0]);
- var announcement = chart.langFormat('accessibility.rangeSelector.clickButtonAnnouncement', { chart: chart,
- axisRangeDescription: axisRangeDescription });
- if (announcement) {
- this.announcer.announce(announcement);
- }
- },
- /**
- * @private
- */
- onInputKbdMove: function (direction) {
- var chart = this.chart;
- var rangeSel = chart.rangeSelector;
- var newIx = chart.highlightedInputRangeIx = (chart.highlightedInputRangeIx || 0) + direction;
- var newIxOutOfRange = newIx > 1 || newIx < 0;
- if (newIxOutOfRange) {
- if (chart.accessibility) {
- chart.accessibility.keyboardNavigation.tabindexContainer.focus();
- chart.accessibility.keyboardNavigation[direction < 0 ? 'prev' : 'next']();
- }
- }
- else if (rangeSel) {
- var svgEl = rangeSel[newIx ? 'maxDateBox' : 'minDateBox'];
- var inputEl = rangeSel[newIx ? 'maxInput' : 'minInput'];
- if (svgEl && inputEl) {
- chart.setFocusToElement(svgEl, inputEl);
- }
- }
- },
- /**
- * @private
- * @param {number} direction
- */
- onInputNavInit: function (direction) {
- var _this = this;
- var component = this;
- var chart = this.chart;
- var buttonIxToHighlight = direction > 0 ? 0 : 1;
- var rangeSel = chart.rangeSelector;
- var svgEl = (rangeSel &&
- rangeSel[buttonIxToHighlight ? 'maxDateBox' : 'minDateBox']);
- var minInput = (rangeSel && rangeSel.minInput);
- var maxInput = (rangeSel && rangeSel.maxInput);
- var inputEl = buttonIxToHighlight ? maxInput : minInput;
- chart.highlightedInputRangeIx = buttonIxToHighlight;
- if (svgEl && minInput && maxInput) {
- chart.setFocusToElement(svgEl, inputEl);
- // Tab-press with the input focused does not propagate to chart
- // automatically, so we manually catch and handle it when relevant.
- if (this.removeInputKeydownHandler) {
- this.removeInputKeydownHandler();
- }
- var keydownHandler = function (e) {
- var isTab = (e.which || e.keyCode) === _this.keyCodes.tab;
- if (isTab) {
- e.preventDefault();
- e.stopPropagation();
- component.onInputKbdMove(e.shiftKey ? -1 : 1);
- }
- };
- var minRemover_1 = addEvent(minInput, 'keydown',
- keydownHandler);
- var maxRemover_1 = addEvent(maxInput, 'keydown',
- keydownHandler);
- this.removeInputKeydownHandler = function () {
- minRemover_1();
- maxRemover_1();
- };
- }
- },
- /**
- * @private
- */
- onInputNavTerminate: function () {
- var rangeSel = (this.chart.rangeSelector || {});
- if (rangeSel.maxInput) {
- rangeSel.hideInput('max');
- }
- if (rangeSel.minInput) {
- rangeSel.hideInput('min');
- }
- if (this.removeInputKeydownHandler) {
- this.removeInputKeydownHandler();
- delete this.removeInputKeydownHandler;
- }
- },
- /**
- * @private
- */
- initDropdownNav: function () {
- var _this = this;
- var chart = this.chart;
- var rangeSelector = chart.rangeSelector;
- var dropdown = (rangeSelector && rangeSelector.dropdown);
- if (rangeSelector && dropdown) {
- chart.setFocusToElement(rangeSelector.buttonGroup, dropdown);
- if (this.removeDropdownKeydownHandler) {
- this.removeDropdownKeydownHandler();
- }
- // Tab-press with dropdown focused does not propagate to chart
- // automatically, so we manually catch and handle it when relevant.
- this.removeDropdownKeydownHandler = addEvent(dropdown, 'keydown', function (e) {
- var isTab = (e.which || e.keyCode) === _this.keyCodes.tab;
- if (isTab) {
- e.preventDefault();
- e.stopPropagation();
- if (chart.accessibility) {
- chart.accessibility.keyboardNavigation.tabindexContainer.focus();
- chart.accessibility.keyboardNavigation[e.shiftKey ? 'prev' : 'next']();
- }
- }
- });
- }
- },
- /**
- * Get navigation for the range selector buttons.
- * @private
- * @return {Highcharts.KeyboardNavigationHandler} The module object.
- */
- getRangeSelectorButtonNavigation: function () {
- var chart = this.chart;
- var keys = this.keyCodes;
- var component = this;
- return new KeyboardNavigationHandler(chart, {
- keyCodeMap: [
- [
- [keys.left, keys.right, keys.up, keys.down],
- function (keyCode) {
- return component.onButtonNavKbdArrowKey(this, keyCode);
- }
- ],
- [
- [keys.enter, keys.space],
- function () {
- return component.onButtonNavKbdClick(this);
- }
- ]
- ],
- validate: function () {
- return !!(chart.rangeSelector &&
- chart.rangeSelector.buttons &&
- chart.rangeSelector.buttons.length);
- },
- init: function (direction) {
- var rangeSelector = chart.rangeSelector;
- if (rangeSelector && rangeSelector.hasVisibleDropdown) {
- component.initDropdownNav();
- }
- else if (rangeSelector) {
- var lastButtonIx = rangeSelector.buttons.length - 1;
- chart.highlightRangeSelectorButton(direction > 0 ? 0 : lastButtonIx);
- }
- },
- terminate: function () {
- if (component.removeDropdownKeydownHandler) {
- component.removeDropdownKeydownHandler();
- delete component.removeDropdownKeydownHandler;
- }
- }
- });
- },
- /**
- * Get navigation for the range selector input boxes.
- * @private
- * @return {Highcharts.KeyboardNavigationHandler}
- * The module object.
- */
- getRangeSelectorInputNavigation: function () {
- var chart = this.chart;
- var component = this;
- return new KeyboardNavigationHandler(chart, {
- keyCodeMap: [],
- validate: function () {
- return shouldRunInputNavigation(chart);
- },
- init: function (direction) {
- component.onInputNavInit(direction);
- },
- terminate: function () {
- component.onInputNavTerminate();
- }
- });
- },
- /**
- * Get keyboard navigation handlers for this component.
- * @return {Array<Highcharts.KeyboardNavigationHandler>}
- * List of module objects.
- */
- getKeyboardNavigation: function () {
- return [
- this.getRangeSelectorButtonNavigation(),
- this.getRangeSelectorInputNavigation()
- ];
- },
- /**
- * Remove component traces
- */
- destroy: function () {
- if (this.removeDropdownKeydownHandler) {
- this.removeDropdownKeydownHandler();
- }
- if (this.removeInputKeydownHandler) {
- this.removeInputKeydownHandler();
- }
- if (this.announcer) {
- this.announcer.destroy();
- }
- }
- });
- return RangeSelectorComponent;
- });
- _registerModule(_modules, 'Accessibility/Components/InfoRegionsComponent.js', [_modules['Core/Renderer/HTML/AST.js'], _modules['Core/Chart/Chart.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/Utils/Announcer.js'], _modules['Accessibility/Components/AnnotationsA11y.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (AST, Chart, F, H, U, AccessibilityComponent, Announcer, AnnotationsA11y, ChartUtilities, HTMLUtilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Accessibility component for chart info region and table.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var format = F.format;
- var doc = H.doc;
- var extend = U.extend,
- pick = U.pick;
- var getAnnotationsInfoHTML = AnnotationsA11y.getAnnotationsInfoHTML;
- var getAxisDescription = ChartUtilities.getAxisDescription,
- getAxisRangeDescription = ChartUtilities.getAxisRangeDescription,
- getChartTitle = ChartUtilities.getChartTitle,
- unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
- var addClass = HTMLUtilities.addClass,
- escapeStringForHTML = HTMLUtilities.escapeStringForHTML,
- getElement = HTMLUtilities.getElement,
- getHeadingTagNameForElement = HTMLUtilities.getHeadingTagNameForElement,
- setElAttrs = HTMLUtilities.setElAttrs,
- stripHTMLTagsFromString = HTMLUtilities.stripHTMLTagsFromString,
- visuallyHideElement = HTMLUtilities.visuallyHideElement;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * @private
- */
- function stripEmptyHTMLTags(str) {
- return str.replace(/<(\w+)[^>]*?>\s*<\/\1>/g, '');
- }
- /**
- * @private
- */
- function getTypeDescForMapChart(chart, formatContext) {
- return formatContext.mapTitle ?
- chart.langFormat('accessibility.chartTypes.mapTypeDescription', formatContext) :
- chart.langFormat('accessibility.chartTypes.unknownMap', formatContext);
- }
- /**
- * @private
- */
- function getTypeDescForCombinationChart(chart, formatContext) {
- return chart.langFormat('accessibility.chartTypes.combinationChart', formatContext);
- }
- /**
- * @private
- */
- function getTypeDescForEmptyChart(chart, formatContext) {
- return chart.langFormat('accessibility.chartTypes.emptyChart', formatContext);
- }
- /**
- * @private
- */
- function buildTypeDescriptionFromSeries(chart, types, context) {
- var firstType = types[0], typeExplaination = chart.langFormat('accessibility.seriesTypeDescriptions.' + firstType, context), multi = chart.series && chart.series.length < 2 ? 'Single' : 'Multiple';
- return (chart.langFormat('accessibility.chartTypes.' + firstType + multi, context) ||
- chart.langFormat('accessibility.chartTypes.default' + multi, context)) + (typeExplaination ? ' ' + typeExplaination : '');
- }
- /**
- * @private
- */
- function getTableSummary(chart) {
- return chart.langFormat('accessibility.table.tableSummary', { chart: chart });
- }
- /**
- * Return simplified explaination of chart type. Some types will not be familiar
- * to most users, but in those cases we try to add an explaination of the type.
- *
- * @private
- * @function Highcharts.Chart#getTypeDescription
- * @param {Array<string>} types The series types in this chart.
- * @return {string} The text description of the chart type.
- */
- Chart.prototype.getTypeDescription = function (types) {
- var firstType = types[0],
- firstSeries = this.series && this.series[0] || {},
- formatContext = {
- numSeries: this.series.length,
- numPoints: firstSeries.points && firstSeries.points.length,
- chart: this,
- mapTitle: firstSeries.mapTitle
- };
- if (!firstType) {
- return getTypeDescForEmptyChart(this, formatContext);
- }
- if (firstType === 'map') {
- return getTypeDescForMapChart(this, formatContext);
- }
- if (this.types.length > 1) {
- return getTypeDescForCombinationChart(this, formatContext);
- }
- return buildTypeDescriptionFromSeries(this, types, formatContext);
- };
- /**
- * The InfoRegionsComponent class
- *
- * @private
- * @class
- * @name Highcharts.InfoRegionsComponent
- */
- var InfoRegionsComponent = function () { };
- InfoRegionsComponent.prototype = new AccessibilityComponent();
- extend(InfoRegionsComponent.prototype, /** @lends Highcharts.InfoRegionsComponent */ {
- /**
- * Init the component
- * @private
- */
- init: function () {
- var chart = this.chart;
- var component = this;
- this.initRegionsDefinitions();
- this.addEvent(chart, 'aftergetTableAST', function (e) {
- component.onDataTableCreated(e);
- });
- this.addEvent(chart, 'afterViewData', function (tableDiv) {
- component.dataTableDiv = tableDiv;
- // Use small delay to give browsers & AT time to register new table
- setTimeout(function () {
- component.focusDataTable();
- }, 300);
- });
- this.announcer = new Announcer(chart, 'assertive');
- },
- /**
- * @private
- */
- initRegionsDefinitions: function () {
- var component = this;
- this.screenReaderSections = {
- before: {
- element: null,
- buildContent: function (chart) {
- var formatter = chart.options.accessibility
- .screenReaderSection.beforeChartFormatter;
- return formatter ? formatter(chart) :
- component.defaultBeforeChartFormatter(chart);
- },
- insertIntoDOM: function (el, chart) {
- chart.renderTo.insertBefore(el, chart.renderTo.firstChild);
- },
- afterInserted: function () {
- if (typeof component.sonifyButtonId !== 'undefined') {
- component.initSonifyButton(component.sonifyButtonId);
- }
- if (typeof component.dataTableButtonId !== 'undefined') {
- component.initDataTableButton(component.dataTableButtonId);
- }
- }
- },
- after: {
- element: null,
- buildContent: function (chart) {
- var formatter = chart.options.accessibility.screenReaderSection
- .afterChartFormatter;
- return formatter ? formatter(chart) :
- component.defaultAfterChartFormatter();
- },
- insertIntoDOM: function (el, chart) {
- chart.renderTo.insertBefore(el, chart.container.nextSibling);
- }
- }
- };
- },
- /**
- * Called on chart render. Have to update the sections on render, in order
- * to get a11y info from series.
- */
- onChartRender: function () {
- var component = this;
- this.linkedDescriptionElement = this.getLinkedDescriptionElement();
- this.setLinkedDescriptionAttrs();
- Object.keys(this.screenReaderSections).forEach(function (regionKey) {
- component.updateScreenReaderSection(regionKey);
- });
- },
- /**
- * @private
- */
- getLinkedDescriptionElement: function () {
- var chartOptions = this.chart.options,
- linkedDescOption = chartOptions.accessibility.linkedDescription;
- if (!linkedDescOption) {
- return;
- }
- if (typeof linkedDescOption !== 'string') {
- return linkedDescOption;
- }
- var query = format(linkedDescOption,
- this.chart),
- queryMatch = doc.querySelectorAll(query);
- if (queryMatch.length === 1) {
- return queryMatch[0];
- }
- },
- /**
- * @private
- */
- setLinkedDescriptionAttrs: function () {
- var el = this.linkedDescriptionElement;
- if (el) {
- el.setAttribute('aria-hidden', 'true');
- addClass(el, 'highcharts-linked-description');
- }
- },
- /**
- * @private
- * @param {string} regionKey The name/key of the region to update
- */
- updateScreenReaderSection: function (regionKey) {
- var chart = this.chart, region = this.screenReaderSections[regionKey], content = region.buildContent(chart), sectionDiv = region.element = (region.element || this.createElement('div')), hiddenDiv = (sectionDiv.firstChild || this.createElement('div'));
- this.setScreenReaderSectionAttribs(sectionDiv, regionKey);
- AST.setElementHTML(hiddenDiv, content);
- sectionDiv.appendChild(hiddenDiv);
- region.insertIntoDOM(sectionDiv, chart);
- visuallyHideElement(hiddenDiv);
- unhideChartElementFromAT(chart, hiddenDiv);
- if (region.afterInserted) {
- region.afterInserted();
- }
- },
- /**
- * @private
- * @param {Highcharts.HTMLDOMElement} sectionDiv The section element
- * @param {string} regionKey Name/key of the region we are setting attrs for
- */
- setScreenReaderSectionAttribs: function (sectionDiv, regionKey) {
- var labelLangKey = ('accessibility.screenReaderSection.' + regionKey + 'RegionLabel'), chart = this.chart, labelText = chart.langFormat(labelLangKey, { chart: chart }), sectionId = 'highcharts-screen-reader-region-' + regionKey + '-' +
- chart.index;
- setElAttrs(sectionDiv, {
- id: sectionId,
- 'aria-label': labelText
- });
- // Sections are wrapped to be positioned relatively to chart in case
- // elements inside are tabbed to.
- sectionDiv.style.position = 'relative';
- if (chart.options.accessibility.landmarkVerbosity === 'all' &&
- labelText) {
- sectionDiv.setAttribute('role', 'region');
- }
- },
- /**
- * @private
- * @return {string}
- */
- defaultBeforeChartFormatter: function () {
- var chart = this.chart,
- format = chart.options.accessibility
- .screenReaderSection.beforeChartFormat,
- axesDesc = this.getAxesDescription(),
- shouldHaveSonifyBtn = (chart.sonify &&
- chart.options.sonification &&
- chart.options.sonification.enabled),
- sonifyButtonId = 'highcharts-a11y-sonify-data-btn-' +
- chart.index,
- dataTableButtonId = 'hc-linkto-highcharts-data-table-' +
- chart.index,
- annotationsList = getAnnotationsInfoHTML(chart),
- annotationsTitleStr = chart.langFormat('accessibility.screenReaderSection.annotations.heading', { chart: chart }),
- context = {
- headingTagName: getHeadingTagNameForElement(chart.renderTo),
- chartTitle: getChartTitle(chart),
- typeDescription: this.getTypeDescriptionText(),
- chartSubtitle: this.getSubtitleText(),
- chartLongdesc: this.getLongdescText(),
- xAxisDescription: axesDesc.xAxis,
- yAxisDescription: axesDesc.yAxis,
- playAsSoundButton: shouldHaveSonifyBtn ?
- this.getSonifyButtonText(sonifyButtonId) : '',
- viewTableButton: chart.getCSV ?
- this.getDataTableButtonText(dataTableButtonId) : '',
- annotationsTitle: annotationsList ? annotationsTitleStr : '',
- annotationsList: annotationsList
- },
- formattedString = H.i18nFormat(format,
- context,
- chart);
- this.dataTableButtonId = dataTableButtonId;
- this.sonifyButtonId = sonifyButtonId;
- return stripEmptyHTMLTags(formattedString);
- },
- /**
- * @private
- * @return {string}
- */
- defaultAfterChartFormatter: function () {
- var chart = this.chart,
- format = chart.options.accessibility
- .screenReaderSection.afterChartFormat,
- context = {
- endOfChartMarker: this.getEndOfChartMarkerText()
- },
- formattedString = H.i18nFormat(format,
- context,
- chart);
- return stripEmptyHTMLTags(formattedString);
- },
- /**
- * @private
- * @return {string}
- */
- getLinkedDescription: function () {
- var el = this.linkedDescriptionElement,
- content = el && el.innerHTML || '';
- return stripHTMLTagsFromString(content);
- },
- /**
- * @private
- * @return {string}
- */
- getLongdescText: function () {
- var chartOptions = this.chart.options,
- captionOptions = chartOptions.caption,
- captionText = captionOptions && captionOptions.text,
- linkedDescription = this.getLinkedDescription();
- return (chartOptions.accessibility.description ||
- linkedDescription ||
- captionText ||
- '');
- },
- /**
- * @private
- * @return {string}
- */
- getTypeDescriptionText: function () {
- var chart = this.chart;
- return chart.types ?
- chart.options.accessibility.typeDescription ||
- chart.getTypeDescription(chart.types) : '';
- },
- /**
- * @private
- * @param {string} buttonId
- * @return {string}
- */
- getDataTableButtonText: function (buttonId) {
- var chart = this.chart,
- buttonText = chart.langFormat('accessibility.table.viewAsDataTableButtonText', { chart: chart,
- chartTitle: getChartTitle(chart) });
- return '<button id="' + buttonId + '">' + buttonText + '</button>';
- },
- /**
- * @private
- * @param {string} buttonId
- * @return {string}
- */
- getSonifyButtonText: function (buttonId) {
- var chart = this.chart;
- if (chart.options.sonification &&
- chart.options.sonification.enabled === false) {
- return '';
- }
- var buttonText = chart.langFormat('accessibility.sonification.playAsSoundButtonText', { chart: chart,
- chartTitle: getChartTitle(chart) });
- return '<button id="' + buttonId + '">' + buttonText + '</button>';
- },
- /**
- * @private
- * @return {string}
- */
- getSubtitleText: function () {
- var subtitle = (this.chart.options.subtitle);
- return stripHTMLTagsFromString(subtitle && subtitle.text || '');
- },
- /**
- * @private
- * @return {string}
- */
- getEndOfChartMarkerText: function () {
- var chart = this.chart, markerText = chart.langFormat('accessibility.screenReaderSection.endOfChartMarker', { chart: chart }), id = 'highcharts-end-of-chart-marker-' + chart.index;
- return '<div id="' + id + '">' + markerText + '</div>';
- },
- /**
- * @private
- * @param {Highcharts.Dictionary<string>} e
- */
- onDataTableCreated: function (e) {
- var chart = this.chart;
- if (chart.options.accessibility.enabled) {
- if (this.viewDataTableButton) {
- this.viewDataTableButton.setAttribute('aria-expanded', 'true');
- }
- var attributes = e.tree.attributes || {};
- attributes.tabindex = -1;
- attributes.summary = getTableSummary(chart);
- e.tree.attributes = attributes;
- }
- },
- /**
- * @private
- */
- focusDataTable: function () {
- var tableDiv = this.dataTableDiv,
- table = tableDiv && tableDiv.getElementsByTagName('table')[0];
- if (table && table.focus) {
- table.focus();
- }
- },
- /**
- * @private
- * @param {string} sonifyButtonId
- */
- initSonifyButton: function (sonifyButtonId) {
- var _this = this;
- var el = this.sonifyButton = getElement(sonifyButtonId);
- var chart = this.chart;
- var defaultHandler = function (e) {
- if (el) {
- el.setAttribute('aria-hidden', 'true');
- el.setAttribute('aria-label', '');
- }
- e.preventDefault();
- e.stopPropagation();
- var announceMsg = chart.langFormat('accessibility.sonification.playAsSoundClickAnnouncement', { chart: chart });
- _this.announcer.announce(announceMsg);
- setTimeout(function () {
- if (el) {
- el.removeAttribute('aria-hidden');
- el.removeAttribute('aria-label');
- }
- if (chart.sonify) {
- chart.sonify();
- }
- }, 1000); // Delay to let screen reader speak the button press
- };
- if (el && chart) {
- setElAttrs(el, {
- tabindex: -1
- });
- el.onclick = function (e) {
- var onPlayAsSoundClick = (chart.options.accessibility &&
- chart.options.accessibility.screenReaderSection.onPlayAsSoundClick);
- (onPlayAsSoundClick || defaultHandler).call(this, e, chart);
- };
- }
- },
- /**
- * Set attribs and handlers for default viewAsDataTable button if exists.
- * @private
- * @param {string} tableButtonId
- */
- initDataTableButton: function (tableButtonId) {
- var el = this.viewDataTableButton = getElement(tableButtonId), chart = this.chart, tableId = tableButtonId.replace('hc-linkto-', '');
- if (el) {
- setElAttrs(el, {
- tabindex: -1,
- 'aria-expanded': !!getElement(tableId)
- });
- el.onclick = chart.options.accessibility
- .screenReaderSection.onViewDataTableClick ||
- function () {
- chart.viewData();
- };
- }
- },
- /**
- * Return object with text description of each of the chart's axes.
- * @private
- * @return {Highcharts.Dictionary<string>}
- */
- getAxesDescription: function () {
- var chart = this.chart,
- shouldDescribeColl = function (collectionKey,
- defaultCondition) {
- var axes = chart[collectionKey];
- return axes.length > 1 || axes[0] &&
- pick(axes[0].options.accessibility &&
- axes[0].options.accessibility.enabled, defaultCondition);
- }, hasNoMap = !!chart.types && chart.types.indexOf('map') < 0, hasCartesian = !!chart.hasCartesianSeries, showXAxes = shouldDescribeColl('xAxis', !chart.angular && hasCartesian && hasNoMap), showYAxes = shouldDescribeColl('yAxis', hasCartesian && hasNoMap), desc = {};
- if (showXAxes) {
- desc.xAxis = this.getAxisDescriptionText('xAxis');
- }
- if (showYAxes) {
- desc.yAxis = this.getAxisDescriptionText('yAxis');
- }
- return desc;
- },
- /**
- * @private
- * @param {string} collectionKey
- * @return {string}
- */
- getAxisDescriptionText: function (collectionKey) {
- var chart = this.chart;
- var axes = chart[collectionKey];
- return chart.langFormat('accessibility.axis.' + collectionKey + 'Description' + (axes.length > 1 ? 'Plural' : 'Singular'), {
- chart: chart,
- names: axes.map(function (axis) {
- return getAxisDescription(axis);
- }),
- ranges: axes.map(function (axis) {
- return getAxisRangeDescription(axis);
- }),
- numAxes: axes.length
- });
- },
- /**
- * Remove component traces
- */
- destroy: function () {
- if (this.announcer) {
- this.announcer.destroy();
- }
- }
- });
- return InfoRegionsComponent;
- });
- _registerModule(_modules, 'Accessibility/Components/ContainerComponent.js', [_modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Core/Globals.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Core/Utilities.js']], function (AccessibilityComponent, KeyboardNavigationHandler, ChartUtilities, H, HTMLUtilities, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Accessibility component for chart container.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT,
- getChartTitle = ChartUtilities.getChartTitle;
- var doc = H.doc;
- var stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString;
- var extend = U.extend;
- /* eslint-disable valid-jsdoc */
- /**
- * The ContainerComponent class
- *
- * @private
- * @class
- * @name Highcharts.ContainerComponent
- */
- var ContainerComponent = function () { };
- ContainerComponent.prototype = new AccessibilityComponent();
- extend(ContainerComponent.prototype, /** @lends Highcharts.ContainerComponent */ {
- /**
- * Called on first render/updates to the chart, including options changes.
- */
- onChartUpdate: function () {
- this.handleSVGTitleElement();
- this.setSVGContainerLabel();
- this.setGraphicContainerAttrs();
- this.setRenderToAttrs();
- this.makeCreditsAccessible();
- },
- /**
- * @private
- */
- handleSVGTitleElement: function () {
- var chart = this.chart, titleId = 'highcharts-title-' + chart.index, titleContents = stripHTMLTags(chart.langFormat('accessibility.svgContainerTitle', {
- chartTitle: getChartTitle(chart)
- }));
- if (titleContents.length) {
- var titleElement = this.svgTitleElement =
- this.svgTitleElement || doc.createElementNS('http://www.w3.org/2000/svg', 'title');
- titleElement.textContent = titleContents;
- titleElement.id = titleId;
- chart.renderTo.insertBefore(titleElement, chart.renderTo.firstChild);
- }
- },
- /**
- * @private
- */
- setSVGContainerLabel: function () {
- var chart = this.chart,
- svgContainerLabel = chart.langFormat('accessibility.svgContainerLabel', {
- chartTitle: getChartTitle(chart)
- });
- if (chart.renderer.box && svgContainerLabel.length) {
- chart.renderer.box.setAttribute('aria-label', svgContainerLabel);
- }
- },
- /**
- * @private
- */
- setGraphicContainerAttrs: function () {
- var chart = this.chart,
- label = chart.langFormat('accessibility.graphicContainerLabel', {
- chartTitle: getChartTitle(chart)
- });
- if (label.length) {
- chart.container.setAttribute('aria-label', label);
- }
- },
- /**
- * @private
- */
- setRenderToAttrs: function () {
- var chart = this.chart;
- if (chart.options.accessibility.landmarkVerbosity !== 'disabled') {
- chart.renderTo.setAttribute('role', 'region');
- }
- else {
- chart.renderTo.removeAttribute('role');
- }
- chart.renderTo.setAttribute('aria-label', chart.langFormat('accessibility.chartContainerLabel', {
- title: getChartTitle(chart),
- chart: chart
- }));
- },
- /**
- * @private
- */
- makeCreditsAccessible: function () {
- var chart = this.chart,
- credits = chart.credits;
- if (credits) {
- if (credits.textStr) {
- credits.element.setAttribute('aria-label', chart.langFormat('accessibility.credits', { creditsStr: stripHTMLTags(credits.textStr) }));
- }
- unhideChartElementFromAT(chart, credits.element);
- }
- },
- /**
- * Empty handler to just set focus on chart
- * @return {Highcharts.KeyboardNavigationHandler}
- */
- getKeyboardNavigation: function () {
- var chart = this.chart;
- return new KeyboardNavigationHandler(chart, {
- keyCodeMap: [],
- validate: function () {
- return true;
- },
- init: function () {
- var a11y = chart.accessibility;
- if (a11y) {
- a11y.keyboardNavigation.tabindexContainer.focus();
- }
- }
- });
- },
- /**
- * Accessibility disabled/chart destroyed.
- */
- destroy: function () {
- this.chart.renderTo.setAttribute('aria-hidden', true);
- }
- });
- return ContainerComponent;
- });
- _registerModule(_modules, 'Accessibility/HighContrastMode.js', [_modules['Core/Globals.js']], function (H) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Handling for Windows High Contrast Mode.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var doc = H.doc,
- isMS = H.isMS,
- win = H.win;
- var whcm = {
- /**
- * Detect WHCM in the browser.
- *
- * @function Highcharts#isHighContrastModeActive
- * @private
- * @return {boolean} Returns true if the browser is in High Contrast mode.
- */
- isHighContrastModeActive: function () {
- // Use media query on Edge, but not on IE
- var isEdge = /(Edg)/.test(win.navigator.userAgent);
- if (win.matchMedia && isEdge) {
- return win.matchMedia('(-ms-high-contrast: active)').matches;
- }
- // Test BG image for IE
- if (isMS && win.getComputedStyle) {
- var testDiv = doc.createElement('div');
- var imageSrc = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
- testDiv.style.backgroundImage = "url(" + imageSrc + ")"; // #13071
- doc.body.appendChild(testDiv);
- var bi = (testDiv.currentStyle ||
- win.getComputedStyle(testDiv)).backgroundImage;
- doc.body.removeChild(testDiv);
- return bi === 'none';
- }
- // Not used for other browsers
- return false;
- },
- /**
- * Force high contrast theme for the chart. The default theme is defined in
- * a separate file.
- *
- * @function Highcharts#setHighContrastTheme
- * @private
- * @param {Highcharts.AccessibilityChart} chart The chart to set the theme of.
- * @return {void}
- */
- setHighContrastTheme: function (chart) {
- // We might want to add additional functionality here in the future for
- // storing the old state so that we can reset the theme if HC mode is
- // disabled. For now, the user will have to reload the page.
- chart.highContrastModeActive = true;
- // Apply theme to chart
- var theme = (chart.options.accessibility.highContrastTheme);
- chart.update(theme, false);
- // Force series colors (plotOptions is not enough)
- chart.series.forEach(function (s) {
- var plotOpts = theme.plotOptions[s.type] || {};
- s.update({
- color: plotOpts.color || 'windowText',
- colors: [plotOpts.color || 'windowText'],
- borderColor: plotOpts.borderColor || 'window'
- });
- // Force point colors if existing
- s.points.forEach(function (p) {
- if (p.options && p.options.color) {
- p.update({
- color: plotOpts.color || 'windowText',
- borderColor: plotOpts.borderColor || 'window'
- }, false);
- }
- });
- });
- // The redraw for each series and after is required for 3D pie
- // (workaround)
- chart.redraw();
- }
- };
- return whcm;
- });
- _registerModule(_modules, 'Accessibility/HighContrastTheme.js', [], function () {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Default theme for Windows High Contrast Mode.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var theme = {
- chart: {
- backgroundColor: 'window'
- },
- title: {
- style: {
- color: 'windowText'
- }
- },
- subtitle: {
- style: {
- color: 'windowText'
- }
- },
- colorAxis: {
- minColor: 'windowText',
- maxColor: 'windowText',
- stops: []
- },
- colors: ['windowText'],
- xAxis: {
- gridLineColor: 'windowText',
- labels: {
- style: {
- color: 'windowText'
- }
- },
- lineColor: 'windowText',
- minorGridLineColor: 'windowText',
- tickColor: 'windowText',
- title: {
- style: {
- color: 'windowText'
- }
- }
- },
- yAxis: {
- gridLineColor: 'windowText',
- labels: {
- style: {
- color: 'windowText'
- }
- },
- lineColor: 'windowText',
- minorGridLineColor: 'windowText',
- tickColor: 'windowText',
- title: {
- style: {
- color: 'windowText'
- }
- }
- },
- tooltip: {
- backgroundColor: 'window',
- borderColor: 'windowText',
- style: {
- color: 'windowText'
- }
- },
- plotOptions: {
- series: {
- lineColor: 'windowText',
- fillColor: 'window',
- borderColor: 'windowText',
- edgeColor: 'windowText',
- borderWidth: 1,
- dataLabels: {
- connectorColor: 'windowText',
- color: 'windowText',
- style: {
- color: 'windowText',
- textOutline: 'none'
- }
- },
- marker: {
- lineColor: 'windowText',
- fillColor: 'windowText'
- }
- },
- pie: {
- color: 'window',
- colors: ['window'],
- borderColor: 'windowText',
- borderWidth: 1
- },
- boxplot: {
- fillColor: 'window'
- },
- candlestick: {
- lineColor: 'windowText',
- fillColor: 'window'
- },
- errorbar: {
- fillColor: 'window'
- }
- },
- legend: {
- backgroundColor: 'window',
- itemStyle: {
- color: 'windowText'
- },
- itemHoverStyle: {
- color: 'windowText'
- },
- itemHiddenStyle: {
- color: '#555'
- },
- title: {
- style: {
- color: 'windowText'
- }
- }
- },
- credits: {
- style: {
- color: 'windowText'
- }
- },
- labels: {
- style: {
- color: 'windowText'
- }
- },
- drilldown: {
- activeAxisLabelStyle: {
- color: 'windowText'
- },
- activeDataLabelStyle: {
- color: 'windowText'
- }
- },
- navigation: {
- buttonOptions: {
- symbolStroke: 'windowText',
- theme: {
- fill: 'window'
- }
- }
- },
- rangeSelector: {
- buttonTheme: {
- fill: 'window',
- stroke: 'windowText',
- style: {
- color: 'windowText'
- },
- states: {
- hover: {
- fill: 'window',
- stroke: 'windowText',
- style: {
- color: 'windowText'
- }
- },
- select: {
- fill: '#444',
- stroke: 'windowText',
- style: {
- color: 'windowText'
- }
- }
- }
- },
- inputBoxBorderColor: 'windowText',
- inputStyle: {
- backgroundColor: 'window',
- color: 'windowText'
- },
- labelStyle: {
- color: 'windowText'
- }
- },
- navigator: {
- handles: {
- backgroundColor: 'window',
- borderColor: 'windowText'
- },
- outlineColor: 'windowText',
- maskFill: 'transparent',
- series: {
- color: 'windowText',
- lineColor: 'windowText'
- },
- xAxis: {
- gridLineColor: 'windowText'
- }
- },
- scrollbar: {
- barBackgroundColor: '#444',
- barBorderColor: 'windowText',
- buttonArrowColor: 'windowText',
- buttonBackgroundColor: 'window',
- buttonBorderColor: 'windowText',
- rifleColor: 'windowText',
- trackBackgroundColor: 'window',
- trackBorderColor: 'windowText'
- }
- };
- return theme;
- });
- _registerModule(_modules, 'Accessibility/Options/Options.js', [_modules['Core/Color/Palette.js']], function (palette) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Default options for accessibility.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- /**
- * Formatter callback for the accessibility announcement.
- *
- * @callback Highcharts.AccessibilityAnnouncementFormatter
- *
- * @param {Array<Highcharts.Series>} updatedSeries
- * Array of all series that received updates. If an announcement is already
- * queued, the series that received updates for that announcement are also
- * included in this array.
- *
- * @param {Highcharts.Series} [addedSeries]
- * This is provided if {@link Highcharts.Chart#addSeries} was called, and there
- * is a new series. In that case, this argument is a reference to the new
- * series.
- *
- * @param {Highcharts.Point} [addedPoint]
- * This is provided if {@link Highcharts.Series#addPoint} was called, and there
- * is a new point. In that case, this argument is a reference to the new point.
- *
- * @return {false|string}
- * The function should return a string with the text to announce to the user.
- * Return empty string to not announce anything. Return `false` to use the
- * default announcement format.
- */
- /**
- * @interface Highcharts.PointAccessibilityOptionsObject
- */ /**
- * Provide a description of the data point, announced to screen readers.
- * @name Highcharts.PointAccessibilityOptionsObject#description
- * @type {string|undefined}
- * @requires modules/accessibility
- * @since 7.1.0
- */ /**
- * Enable or disable exposing the point to assistive technology
- * @name Highcharts.PointAccessibilityOptionsObject#enabled
- * @type {boolean|undefined}
- * @requires modules/accessibility
- * @since 9.0.1
- */
- /* *
- * @interface Highcharts.PointOptionsObject in parts/Point.ts
- */ /**
- * @name Highcharts.PointOptionsObject#accessibility
- * @type {Highcharts.PointAccessibilityOptionsObject|undefined}
- * @requires modules/accessibility
- * @since 7.1.0
- */
- /**
- * @callback Highcharts.ScreenReaderClickCallbackFunction
- *
- * @param {global.MouseEvent} evt
- * Mouse click event
- *
- * @return {void}
- */
- /**
- * Creates a formatted string for the screen reader module.
- *
- * @callback Highcharts.ScreenReaderFormatterCallbackFunction<T>
- *
- * @param {T} context
- * Context to format
- *
- * @return {string}
- * Formatted string for the screen reader module.
- */
- var Options = {
- /**
- * 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](https://www.highcharts.com/docs/accessibility/accessibility-module).
- *
- * @since 5.0.0
- * @requires modules/accessibility
- * @optionparent accessibility
- */
- accessibility: {
- /**
- * Enable accessibility functionality for the chart.
- *
- * @since 5.0.0
- */
- enabled: true,
- /**
- * Accessibility options for the screen reader information sections
- * added before and after the chart.
- *
- * @since 8.0.0
- */
- screenReaderSection: {
- /**
- * 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 {Highcharts.ScreenReaderClickCallbackFunction}
- * @since 8.0.0
- * @apioption accessibility.screenReaderSection.onViewDataTableClick
- */
- /**
- * Function to run upon clicking the "Play as sound" button in
- * the screen reader region.
- *
- * By default Highcharts will call the `chart.sonify` function.
- *
- * @type {Highcharts.ScreenReaderClickCallbackFunction}
- * @since 8.0.1
- * @apioption accessibility.screenReaderSection.onPlayAsSoundClick
- */
- /**
- * A formatter function to create the HTML contents of the hidden
- * screen reader information region before the chart. Receives one
- * argument, `chart`, referring to the chart object. Should return a
- * string with the HTML content of the region. By default this
- * returns an automatic description of the chart based on
- * [beforeChartFormat](#accessibility.screenReaderSection.beforeChartFormat).
- *
- * @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Chart>}
- * @since 8.0.0
- * @apioption accessibility.screenReaderSection.beforeChartFormatter
- */
- /**
- * Format for the screen reader information region before the chart.
- * Supported HTML tags are `<h1-6>`, `<p>`, `<div>`, `<a>`, `<ul>`,
- * `<ol>`, `<li>`, and `<button>`. Attributes are not supported,
- * except for id on `<div>`, `<a>`, and `<button>`. Id is required
- * on `<a>` and `<button>` in the format `<tag id="abcd">`. Numbers,
- * lower- and uppercase letters, "-" and "#" are valid characters in
- * IDs.
- *
- * The headingTagName is an auto-detected heading (h1-h6) that
- * corresponds to the heading level below the previous heading in
- * the DOM.
- *
- * @since 8.0.0
- */
- beforeChartFormat: '<{headingTagName}>{chartTitle}</{headingTagName}>' +
- '<div>{typeDescription}</div>' +
- '<div>{chartSubtitle}</div>' +
- '<div>{chartLongdesc}</div>' +
- '<div>{playAsSoundButton}</div>' +
- '<div>{viewTableButton}</div>' +
- '<div>{xAxisDescription}</div>' +
- '<div>{yAxisDescription}</div>' +
- '<div>{annotationsTitle}{annotationsList}</div>',
- /**
- * A formatter function to create the HTML contents of the hidden
- * screen reader information region after the chart. Analogous to
- * [beforeChartFormatter](#accessibility.screenReaderSection.beforeChartFormatter).
- *
- * @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Chart>}
- * @since 8.0.0
- * @apioption accessibility.screenReaderSection.afterChartFormatter
- */
- /**
- * Format for the screen reader information region after the chart.
- * Analogous to [beforeChartFormat](#accessibility.screenReaderSection.beforeChartFormat).
- *
- * @since 8.0.0
- */
- afterChartFormat: '{endOfChartMarker}',
- /**
- * Date format to use to describe range of datetime axes.
- *
- * For an overview of the replacement codes, see
- * [dateFormat](/class-reference/Highcharts#.dateFormat).
- *
- * @see [point.dateFormat](#accessibility.point.dateFormat)
- *
- * @since 8.0.0
- */
- axisRangeDateFormat: '%Y-%m-%d %H:%M:%S'
- },
- /**
- * Accessibility options global to all data series. Individual series
- * can also have specific [accessibility options](#plotOptions.series.accessibility)
- * set.
- *
- * @since 8.0.0
- */
- series: {
- /**
- * 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. If `false` is returned,
- * the default formatter will be used for that series.
- *
- * @see [series.description](#plotOptions.series.description)
- *
- * @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Series>}
- * @since 8.0.0
- * @apioption accessibility.series.descriptionFormatter
- */
- /**
- * Whether or not to add series descriptions to charts with a single
- * series.
- *
- * @since 8.0.0
- */
- describeSingleSeries: false,
- /**
- * 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 {boolean|number}
- * @since 8.0.0
- */
- pointDescriptionEnabledThreshold: 200
- },
- /**
- * Options for descriptions of individual data points.
- *
- * @since 8.0.0
- */
- point: {
- /**
- * 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 [dateFormatter](#accessibility.point.dateFormatter)
- *
- * @type {string}
- * @since 8.0.0
- * @apioption accessibility.point.dateFormat
- */
- /**
- * 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 [dateFormat](#accessibility.point.dateFormat)
- *
- * @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Point>}
- * @since 8.0.0
- * @apioption accessibility.point.dateFormatter
- */
- /**
- * Prefix to add to the values in the point descriptions. Uses
- * [tooltip.valuePrefix](#tooltip.valuePrefix) if not defined.
- *
- * @type {string}
- * @since 8.0.0
- * @apioption accessibility.point.valuePrefix
- */
- /**
- * Suffix to add to the values in the point descriptions. Uses
- * [tooltip.valueSuffix](#tooltip.valueSuffix) if not defined.
- *
- * @type {string}
- * @since 8.0.0
- * @apioption accessibility.point.valueSuffix
- */
- /**
- * Decimals to use for the values in the point descriptions. Uses
- * [tooltip.valueDecimals](#tooltip.valueDecimals) if not defined.
- *
- * @type {number}
- * @since 8.0.0
- * @apioption accessibility.point.valueDecimals
- */
- /**
- * 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. If `false` is returned, the
- * default formatter will be used for that point.
- *
- * Note: Prefer using [accessibility.point.valueDescriptionFormat](#accessibility.point.valueDescriptionFormat)
- * instead if possible, as default functionality such as describing
- * annotations will be preserved.
- *
- * @see [accessibility.point.valueDescriptionFormat](#accessibility.point.valueDescriptionFormat)
- * @see [point.accessibility.description](#series.line.data.accessibility.description)
- *
- * @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Point>}
- * @since 8.0.0
- * @apioption accessibility.point.descriptionFormatter
- */
- /**
- * Format to use for describing the values of data points
- * to assistive technology - including screen readers.
- * The point context is available as `{point}`.
- *
- * Additionally, the series name, annotation info, and
- * description added in `point.accessibility.description`
- * is added by default if relevant. To override this, use the
- * [accessibility.point.descriptionFormatter](#accessibility.point.descriptionFormatter)
- * option.
- *
- * @see [point.accessibility.description](#series.line.data.accessibility.description)
- * @see [accessibility.point.descriptionFormatter](#accessibility.point.descriptionFormatter)
- *
- * @type {string}
- * @since 8.0.1
- */
- valueDescriptionFormat: '{index}. {xDescription}{separator}{value}.'
- },
- /**
- * Amount of landmarks/regions to create for screen reader users. More
- * landmarks can make navigation with screen readers easier, but can
- * be distracting if there are lots of charts on the page. Three modes
- * are available:
- * - `all`: Adds regions for all series, legend, menu, information
- * region.
- * - `one`: Adds a single landmark per chart.
- * - `disabled`: No landmarks are added.
- *
- * @since 7.1.0
- * @validvalue ["all", "one", "disabled"]
- */
- landmarkVerbosity: 'all',
- /**
- * Link the chart to an HTML element describing the contents of the
- * chart.
- *
- * It is always recommended to describe charts using visible text, to
- * improve SEO as well as accessibility for users with disabilities.
- * This option lets an HTML element with a description be linked to the
- * chart, so that screen reader users can connect the two.
- *
- * By setting this option to a string, Highcharts runs the string as an
- * HTML selector query on the entire document. If there is only a single
- * match, this element is linked to the chart. The content of the linked
- * element will be included in the chart description for screen reader
- * users.
- *
- * By default, the chart looks for an adjacent sibling element with the
- * `highcharts-description` class.
- *
- * The feature can be disabled by setting the option to an empty string,
- * or overridden by providing the
- * [accessibility.description](#accessibility.description) option.
- * Alternatively, the HTML element to link can be passed in directly as
- * an HTML node.
- *
- * If you need the description to be part of the exported image,
- * consider using the [caption](#caption) feature.
- *
- * If you need the description to be hidden visually, use the
- * [accessibility.description](#accessibility.description) option.
- *
- * @see [caption](#caption)
- * @see [description](#accessibility.description)
- * @see [typeDescription](#accessibility.typeDescription)
- *
- * @sample highcharts/accessibility/accessible-line
- * Accessible line chart
- *
- * @type {string|Highcharts.HTMLDOMElement}
- * @since 8.0.0
- */
- linkedDescription: '*[data-highcharts-chart="{index}"] + .highcharts-description',
- /**
- * A hook for adding custom components to the accessibility module.
- * Should be an object mapping component names to instances of classes
- * inheriting from the Highcharts.AccessibilityComponent base class.
- * Remember to add the component to the
- * [keyboardNavigation.order](#accessibility.keyboardNavigation.order)
- * for the keyboard navigation to be usable.
- *
- * @sample highcharts/accessibility/custom-component
- * Custom accessibility component
- *
- * @type {*}
- * @since 7.1.0
- * @apioption accessibility.customComponents
- */
- /**
- * Theme to apply to the chart when Windows High Contrast Mode is
- * detected. By default, a high contrast theme matching the high
- * contrast system system colors is used.
- *
- * @type {*}
- * @since 7.1.3
- * @apioption accessibility.highContrastTheme
- */
- /**
- * A text description of the chart.
- *
- * **Note: Prefer using [linkedDescription](#accessibility.linkedDescription)
- * or [caption](#caption.text) instead.**
- *
- * If the Accessibility module is loaded, this option is included by
- * default as a long description of the chart in the hidden screen
- * reader information region.
- *
- * Note: Since Highcharts now supports captions and linked descriptions,
- * it is preferred to define the description using those methods, as a
- * visible caption/description benefits all users. If the
- * `accessibility.description` option is defined, the linked description
- * is ignored, and the caption is hidden from screen reader users.
- *
- * @see [linkedDescription](#accessibility.linkedDescription)
- * @see [caption](#caption)
- * @see [typeDescription](#accessibility.typeDescription)
- *
- * @type {string}
- * @since 5.0.0
- * @apioption accessibility.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 accessibility.typeDescription
- */
- /**
- * Options for keyboard navigation.
- *
- * @declare Highcharts.KeyboardNavigationOptionsObject
- * @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
- *
- * @declare Highcharts.KeyboardNavigationFocusBorderOptionsObject
- * @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}
- * @since 6.0.3
- */
- style: {
- /** @internal */
- color: palette.highlightColor80,
- /** @internal */
- lineWidth: 2,
- /** @internal */
- borderRadius: 3
- },
- /**
- * Focus border margin around the elements.
- *
- * @since 6.0.3
- */
- margin: 2
- },
- /**
- * Order of tab navigation in the chart. Determines which elements
- * are tabbed to first. Available elements are: `series`, `zoom`,
- * `rangeSelector`, `chartMenu`, `legend` and `container`. In
- * addition, any custom components can be added here. Adding
- * `container` first in order will make the keyboard focus stop on
- * the chart container first, requiring the user to tab again to
- * enter the chart.
- *
- * @type {Array<string>}
- * @since 7.1.0
- */
- order: ['series', 'zoom', 'rangeSelector', 'legend', 'chartMenu'],
- /**
- * Whether or not to wrap around when reaching the end of arrow-key
- * navigation for an element in the chart.
- * @since 7.1.0
- */
- wrapAround: true,
- /**
- * Options for the keyboard navigation of data points and series.
- *
- * @declare Highcharts.KeyboardNavigationSeriesNavigationOptionsObject
- * @since 8.0.0
- */
- seriesNavigation: {
- /**
- * 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 can be useful for
- * unifying navigation behavior with/without screen readers
- * enabled.
- *
- * @type {string}
- * @default normal
- * @since 8.0.0
- * @validvalue ["normal", "serialize"]
- * @apioption accessibility.keyboardNavigation.seriesNavigation.mode
- */
- /**
- * Skip null points when navigating through points with the
- * keyboard.
- *
- * @since 8.0.0
- */
- skipNullPoints: true,
- /**
- * When a series contains more points than this, we no longer
- * allow keyboard navigation for it.
- *
- * Set to `false` to disable.
- *
- * @type {boolean|number}
- * @since 8.0.0
- */
- pointNavigationEnabledThreshold: false
- }
- },
- /**
- * Options for announcing new data to screen reader users. Useful
- * for dynamic data applications and drilldown.
- *
- * Keep in mind that frequent announcements will not be useful to
- * users, as they won't have time to explore the new data. For these
- * applications, consider making snapshots of the data accessible, and
- * do the announcements in batches.
- *
- * @declare Highcharts.AccessibilityAnnounceNewDataOptionsObject
- * @since 7.1.0
- */
- announceNewData: {
- /**
- * Optional formatter callback for the announcement. Receives
- * up to three arguments. The first argument is always an array
- * of all series that received updates. If an announcement is
- * already queued, the series that received updates for that
- * announcement are also included in this array. The second
- * argument is provided if `chart.addSeries` was called, and
- * there is a new series. In that case, this argument is a
- * reference to the new series. The third argument, similarly,
- * is provided if `series.addPoint` was called, and there is a
- * new point. In that case, this argument is a reference to the
- * new point.
- *
- * The function should return a string with the text to announce
- * to the user. Return empty string to not announce anything.
- * Return `false` to use the default announcement format.
- *
- * @sample highcharts/accessibility/custom-dynamic
- * High priority live alerts
- *
- * @type {Highcharts.AccessibilityAnnouncementFormatter}
- * @apioption accessibility.announceNewData.announcementFormatter
- */
- /**
- * Enable announcing new data to screen reader users
- * @sample highcharts/accessibility/accessible-dynamic
- * Dynamic data accessible
- */
- enabled: false,
- /**
- * Minimum interval between announcements in milliseconds. If
- * new data arrives before this amount of time has passed, it is
- * queued for announcement. If another new data event happens
- * while an announcement is queued, the queued announcement is
- * dropped, and the latest announcement is queued instead. Set
- * to 0 to allow all announcements, but be warned that frequent
- * announcements are disturbing to users.
- */
- minAnnounceInterval: 5000,
- /**
- * Choose whether or not the announcements should interrupt the
- * screen reader. If not enabled, the user will be notified once
- * idle. It is recommended not to enable this setting unless
- * there is a specific reason to do so.
- */
- interruptUser: false
- }
- },
- /**
- * Accessibility options for a data point.
- *
- * @declare Highcharts.PointAccessibilityOptionsObject
- * @since 7.1.0
- * @apioption series.line.data.accessibility
- */
- /**
- * Provide a description of the data point, announced to screen readers.
- *
- * @type {string}
- * @since 7.1.0
- * @apioption series.line.data.accessibility.description
- */
- /**
- * Set to false to disable accessibility functionality for a specific point.
- * The point will not be included in keyboard navigation, and will not be
- * exposed to assistive technology.
- *
- * @type {boolean}
- * @since 9.0.1
- * @apioption series.line.data.accessibility.enabled
- */
- /**
- * Accessibility options for a series.
- *
- * @declare Highcharts.SeriesAccessibilityOptionsObject
- * @since 7.1.0
- * @requires modules/accessibility
- * @apioption plotOptions.series.accessibility
- */
- /**
- * Enable/disable accessibility functionality for a specific series.
- *
- * @type {boolean}
- * @since 7.1.0
- * @apioption plotOptions.series.accessibility.enabled
- */
- /**
- * Provide a description of the series, announced to screen readers.
- *
- * @type {string}
- * @since 7.1.0
- * @apioption plotOptions.series.accessibility.description
- */
- /**
- * Formatter function to use instead of the default for point
- * descriptions. Same as `accessibility.point.descriptionFormatter`, but for
- * a single series.
- *
- * @see [accessibility.point.descriptionFormatter](#accessibility.point.descriptionFormatter)
- *
- * @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Point>}
- * @since 7.1.0
- * @apioption plotOptions.series.accessibility.pointDescriptionFormatter
- */
- /**
- * Expose only the series element to screen readers, not its points.
- *
- * @type {boolean}
- * @since 7.1.0
- * @apioption plotOptions.series.accessibility.exposeAsGroupOnly
- */
- /**
- * Keyboard navigation for a series
- *
- * @declare Highcharts.SeriesAccessibilityKeyboardNavigationOptionsObject
- * @since 7.1.0
- * @apioption plotOptions.series.accessibility.keyboardNavigation
- */
- /**
- * Enable/disable keyboard navigation support for a specific series.
- *
- * @type {boolean}
- * @since 7.1.0
- * @apioption plotOptions.series.accessibility.keyboardNavigation.enabled
- */
- /**
- * Accessibility options for an annotation label.
- *
- * @declare Highcharts.AnnotationLabelAccessibilityOptionsObject
- * @since 8.0.1
- * @requires modules/accessibility
- * @apioption annotations.labelOptions.accessibility
- */
- /**
- * Description of an annotation label for screen readers and other assistive
- * technology.
- *
- * @type {string}
- * @since 8.0.1
- * @apioption annotations.labelOptions.accessibility.description
- */
- /**
- * Accessibility options for an axis. Requires the accessibility module.
- *
- * @declare Highcharts.AxisAccessibilityOptionsObject
- * @since 7.1.0
- * @requires modules/accessibility
- * @apioption xAxis.accessibility
- */
- /**
- * Enable axis accessibility features, including axis information in the
- * screen reader information region. If this is disabled on the xAxis, the
- * x values are not exposed to screen readers for the individual data points
- * by default.
- *
- * @type {boolean}
- * @since 7.1.0
- * @apioption xAxis.accessibility.enabled
- */
- /**
- * Description for an axis to expose to screen reader users.
- *
- * @type {string}
- * @since 7.1.0
- * @apioption xAxis.accessibility.description
- */
- /**
- * Range description for an axis. Overrides the default range description.
- * Set to empty to disable range description for this axis.
- *
- * @type {string}
- * @since 7.1.0
- * @apioption xAxis.accessibility.rangeDescription
- */
- /**
- * @optionparent legend
- */
- legend: {
- /**
- * Accessibility options for the legend. Requires the Accessibility
- * module.
- *
- * @since 7.1.0
- * @requires modules/accessibility
- */
- accessibility: {
- /**
- * Enable accessibility support for the legend.
- *
- * @since 7.1.0
- */
- enabled: true,
- /**
- * Options for keyboard navigation for the legend.
- *
- * @since 7.1.0
- * @requires modules/accessibility
- */
- keyboardNavigation: {
- /**
- * Enable keyboard navigation for the legend.
- *
- * @see [accessibility.keyboardNavigation](#accessibility.keyboardNavigation.enabled)
- *
- * @since 7.1.0
- */
- enabled: true
- }
- }
- },
- /**
- * @optionparent exporting
- */
- exporting: {
- /**
- * Accessibility options for the exporting menu. Requires the
- * Accessibility module.
- *
- * @since 7.1.0
- * @requires modules/accessibility
- */
- accessibility: {
- /**
- * Enable accessibility support for the export menu.
- *
- * @since 7.1.0
- */
- enabled: true
- }
- }
- };
- return Options;
- });
- _registerModule(_modules, 'Accessibility/Options/LangOptions.js', [], function () {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Default lang/i18n options for accessibility.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var langOptions = {
- /**
- * Configure the accessibility strings in 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](https://www.highcharts.com/docs/chart-concepts/accessibility).
- *
- * For more dynamic control over the accessibility functionality, see
- * [accessibility.point.descriptionFormatter](#accessibility.point.descriptionFormatter),
- * [accessibility.series.descriptionFormatter](#accessibility.series.descriptionFormatter),
- * and
- * [accessibility.screenReaderSection.beforeChartFormatter](#accessibility.screenReaderSection.beforeChartFormatter).
- *
- * @since 6.0.6
- * @optionparent lang.accessibility
- */
- accessibility: {
- defaultChartTitle: 'Chart',
- chartContainerLabel: '{title}. Highcharts interactive chart.',
- svgContainerLabel: 'Interactive chart',
- drillUpButton: '{buttonText}',
- credits: 'Chart credits: {creditsStr}',
- /**
- * Thousands separator to use when formatting numbers for screen
- * readers. Note that many screen readers will not handle space as a
- * thousands separator, and will consider "11 700" as two numbers.
- *
- * Set to `null` to use the separator defined in
- * [lang.thousandsSep](lang.thousandsSep).
- *
- * @since 7.1.0
- */
- thousandsSep: ',',
- /**
- * 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: '',
- /**
- * Set a label on the container wrapping the SVG.
- *
- * @see [chartContainerLabel](#lang.accessibility.chartContainerLabel)
- *
- * @since 8.0.0
- */
- graphicContainerLabel: '',
- /**
- * Language options for the screen reader information sections added
- * before and after the charts.
- *
- * @since 8.0.0
- */
- screenReaderSection: {
- beforeRegionLabel: 'Chart screen reader information.',
- afterRegionLabel: '',
- /**
- * Language options for annotation descriptions.
- *
- * @since 8.0.1
- */
- annotations: {
- heading: 'Chart annotations summary',
- descriptionSinglePoint: '{annotationText}. Related to {annotationPoint}',
- descriptionMultiplePoints: '{annotationText}. Related to {annotationPoint}' +
- '{ Also related to, #each(additionalAnnotationPoints)}',
- descriptionNoPoints: '{annotationText}'
- },
- /**
- * Label for the end of the chart. Announced by screen readers.
- *
- * @since 8.0.0
- */
- endOfChartMarker: 'End of interactive chart.'
- },
- /**
- * Language options for sonification.
- *
- * @since 8.0.1
- */
- sonification: {
- playAsSoundButtonText: 'Play as sound, {chartTitle}',
- playAsSoundClickAnnouncement: 'Play'
- },
- /**
- * Language options for accessibility of the legend.
- *
- * @since 8.0.0
- */
- legend: {
- legendLabelNoTitle: 'Toggle series visibility',
- legendLabel: 'Chart legend: {legendTitle}',
- legendItem: 'Show {itemName}'
- },
- /**
- * Chart and map zoom accessibility language options.
- *
- * @since 8.0.0
- */
- zoom: {
- mapZoomIn: 'Zoom chart',
- mapZoomOut: 'Zoom out chart',
- resetZoomButton: 'Reset zoom'
- },
- /**
- * Range selector language options for accessibility.
- *
- * @since 8.0.0
- */
- rangeSelector: {
- dropdownLabel: '{rangeTitle}',
- minInputLabel: 'Select start date.',
- maxInputLabel: 'Select end date.',
- clickButtonAnnouncement: 'Viewing {axisRangeDescription}'
- },
- /**
- * Accessibility language options for the data table.
- *
- * @since 8.0.0
- */
- table: {
- viewAsDataTableButtonText: 'View as data table, {chartTitle}',
- tableSummary: 'Table representation of chart.'
- },
- /**
- * Default announcement for new data in charts. If addPoint or
- * addSeries is used, and only one series/point is added, the
- * `newPointAnnounce` and `newSeriesAnnounce` strings are used.
- * The `...Single` versions will be used if there is only one chart
- * on the page, and the `...Multiple` versions will be used if there
- * are multiple charts on the page. For all other new data events,
- * the `newDataAnnounce` string will be used.
- *
- * @since 7.1.0
- */
- announceNewData: {
- newDataAnnounce: 'Updated data for chart {chartTitle}',
- newSeriesAnnounceSingle: 'New data series: {seriesDesc}',
- newPointAnnounceSingle: 'New data point: {pointDesc}',
- newSeriesAnnounceMultiple: 'New data series in chart {chartTitle}: {seriesDesc}',
- newPointAnnounceMultiple: 'New data point in chart {chartTitle}: {pointDesc}'
- },
- /**
- * 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.'
- },
- /**
- * Axis description format strings.
- *
- * @since 6.0.6
- */
- axis: {
- /* eslint-disable max-len */
- xAxisDescriptionSingular: 'The chart has 1 X axis displaying {names[0]}. {ranges[0]}',
- xAxisDescriptionPlural: 'The chart has {numAxes} X axes displaying {#each(names, -1) }and {names[-1]}.',
- yAxisDescriptionSingular: 'The chart has 1 Y axis displaying {names[0]}. {ranges[0]}',
- yAxisDescriptionPlural: 'The chart has {numAxes} Y axes displaying {#each(names, -1) }and {names[-1]}.',
- timeRangeDays: 'Range: {range} days.',
- timeRangeHours: 'Range: {range} hours.',
- timeRangeMinutes: 'Range: {range} minutes.',
- timeRangeSeconds: 'Range: {range} seconds.',
- rangeFromTo: 'Range: {rangeFrom} to {rangeTo}.',
- rangeCategories: 'Range: {numCategories} categories.'
- },
- /**
- * Exporting menu format strings for accessibility module.
- *
- * @since 6.0.6
- */
- exporting: {
- chartMenuLabel: 'Chart menu',
- menuButtonLabel: 'View chart menu',
- exportRegionLabel: 'Chart 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)}.'
- },
- /**
- * User supplied description text. This is added in the point
- * comment description by default 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}',
- /**
- * Description for the value of null points.
- *
- * @since 8.0.0
- */
- nullPointValue: 'No value',
- /**
- * Description for annotations on a point, as it is made available
- * to assistive technology.
- *
- * @since 8.0.1
- */
- pointAnnotationsDescription: '{Annotation: #each(annotations). }'
- }
- }
- };
- return langOptions;
- });
- _registerModule(_modules, 'Accessibility/Options/DeprecatedOptions.js', [_modules['Core/Utilities.js']], function (U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Default options for accessibility.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- /* eslint-disable max-len */
- /*
- * List of deprecated options:
- *
- * chart.description -> accessibility.description
- * chart.typeDescription -> accessibility.typeDescription
- * series.description -> series.accessibility.description
- * series.exposeElementToA11y -> series.accessibility.exposeAsGroupOnly
- * series.pointDescriptionFormatter ->
- * series.accessibility.pointDescriptionFormatter
- * series.skipKeyboardNavigation ->
- * series.accessibility.keyboardNavigation.enabled
- * point.description -> point.accessibility.description !!!! WARNING: No longer deprecated and handled, removed for HC8.
- * axis.description -> axis.accessibility.description
- *
- * accessibility.pointDateFormat -> accessibility.point.dateFormat
- * accessibility.addTableShortcut -> Handled by screenReaderSection.beforeChartFormat
- * accessibility.pointDateFormatter -> accessibility.point.dateFormatter
- * accessibility.pointDescriptionFormatter -> accessibility.point.descriptionFormatter
- * accessibility.pointDescriptionThreshold -> accessibility.series.pointDescriptionEnabledThreshold
- * accessibility.pointNavigationThreshold -> accessibility.keyboardNavigation.seriesNavigation.pointNavigationEnabledThreshold
- * accessibility.pointValueDecimals -> accessibility.point.valueDecimals
- * accessibility.pointValuePrefix -> accessibility.point.valuePrefix
- * accessibility.pointValueSuffix -> accessibility.point.valueSuffix
- * accessibility.screenReaderSectionFormatter -> accessibility.screenReaderSection.beforeChartFormatter
- * accessibility.describeSingleSeries -> accessibility.series.describeSingleSeries
- * accessibility.seriesDescriptionFormatter -> accessibility.series.descriptionFormatter
- * accessibility.onTableAnchorClick -> accessibility.screenReaderSection.onViewDataTableClick
- * accessibility.axisRangeDateFormat -> accessibility.screenReaderSection.axisRangeDateFormat
- * accessibility.keyboardNavigation.skipNullPoints -> accessibility.keyboardNavigation.seriesNavigation.skipNullPoints
- * accessibility.keyboardNavigation.mode -> accessibility.keyboardNavigation.seriesNavigation.mode
- *
- * lang.accessibility.chartHeading -> no longer used, remove
- * lang.accessibility.legendItem -> lang.accessibility.legend.legendItem
- * lang.accessibility.legendLabel -> lang.accessibility.legend.legendLabel
- * lang.accessibility.mapZoomIn -> lang.accessibility.zoom.mapZoomIn
- * lang.accessibility.mapZoomOut -> lang.accessibility.zoom.mapZoomOut
- * lang.accessibility.resetZoomButton -> lang.accessibility.zoom.resetZoomButton
- * lang.accessibility.screenReaderRegionLabel -> lang.accessibility.screenReaderSection.beforeRegionLabel
- * lang.accessibility.rangeSelectorButton -> lang.accessibility.rangeSelector.buttonText
- * lang.accessibility.rangeSelectorMaxInput -> lang.accessibility.rangeSelector.maxInputLabel
- * lang.accessibility.rangeSelectorMinInput -> lang.accessibility.rangeSelector.minInputLabel
- * lang.accessibility.svgContainerEnd -> lang.accessibility.screenReaderSection.endOfChartMarker
- * lang.accessibility.viewAsDataTable -> lang.accessibility.table.viewAsDataTableButtonText
- * lang.accessibility.tableSummary -> lang.accessibility.table.tableSummary
- *
- */
- /* eslint-enable max-len */
- var error = U.error,
- pick = U.pick;
- /* eslint-disable valid-jsdoc */
- /**
- * Set a new option on a root prop, where the option is defined as an array of
- * suboptions.
- * @private
- * @param root
- * @param {Array<string>} optionAsArray
- * @param {*} val
- * @return {void}
- */
- function traverseSetOption(root, optionAsArray, val) {
- var opt = root,
- prop,
- i = 0;
- for (; i < optionAsArray.length - 1; ++i) {
- prop = optionAsArray[i];
- opt = opt[prop] = pick(opt[prop], {});
- }
- opt[optionAsArray[optionAsArray.length - 1]] = val;
- }
- /**
- * If we have a clear root option node for old and new options and a mapping
- * between, we can use this generic function for the copy and warn logic.
- */
- function deprecateFromOptionsMap(chart, rootOldAsArray, rootNewAsArray, mapToNewOptions) {
- /**
- * @private
- */
- function getChildProp(root, propAsArray) {
- return propAsArray.reduce(function (acc, cur) {
- return acc[cur];
- }, root);
- }
- var rootOld = getChildProp(chart.options,
- rootOldAsArray),
- rootNew = getChildProp(chart.options,
- rootNewAsArray);
- Object.keys(mapToNewOptions).forEach(function (oldOptionKey) {
- var _a;
- var val = rootOld[oldOptionKey];
- if (typeof val !== 'undefined') {
- traverseSetOption(rootNew, mapToNewOptions[oldOptionKey], val);
- error(32, false, chart, (_a = {},
- _a[rootOldAsArray.join('.') + "." + oldOptionKey] = rootNewAsArray.join('.') + "." + mapToNewOptions[oldOptionKey].join('.'),
- _a));
- }
- });
- }
- /**
- * @private
- */
- function copyDeprecatedChartOptions(chart) {
- var chartOptions = chart.options.chart,
- a11yOptions = chart.options.accessibility || {};
- ['description', 'typeDescription'].forEach(function (prop) {
- var _a;
- if (chartOptions[prop]) {
- a11yOptions[prop] = chartOptions[prop];
- error(32, false, chart, (_a = {}, _a["chart." + prop] = "use accessibility." + prop, _a));
- }
- });
- }
- /**
- * @private
- */
- function copyDeprecatedAxisOptions(chart) {
- chart.axes.forEach(function (axis) {
- var opts = axis.options;
- if (opts && opts.description) {
- opts.accessibility = opts.accessibility || {};
- opts.accessibility.description = opts.description;
- error(32, false, chart, { 'axis.description': 'use axis.accessibility.description' });
- }
- });
- }
- /**
- * @private
- */
- function copyDeprecatedSeriesOptions(chart) {
- // Map of deprecated series options. New options are defined as
- // arrays of paths under series.options.
- var oldToNewSeriesOptions = {
- description: ['accessibility', 'description'],
- exposeElementToA11y: ['accessibility', 'exposeAsGroupOnly'],
- pointDescriptionFormatter: [
- 'accessibility', 'pointDescriptionFormatter'
- ],
- skipKeyboardNavigation: [
- 'accessibility', 'keyboardNavigation', 'enabled'
- ]
- };
- chart.series.forEach(function (series) {
- // Handle series wide options
- Object.keys(oldToNewSeriesOptions).forEach(function (oldOption) {
- var _a;
- var optionVal = series.options[oldOption];
- if (typeof optionVal !== 'undefined') {
- // Set the new option
- traverseSetOption(series.options, oldToNewSeriesOptions[oldOption],
- // Note that skipKeyboardNavigation has inverted option
- // value, since we set enabled rather than disabled
- oldOption === 'skipKeyboardNavigation' ?
- !optionVal : optionVal);
- error(32, false, chart, (_a = {}, _a["series." + oldOption] = "series." + oldToNewSeriesOptions[oldOption].join('.'), _a));
- }
- });
- });
- }
- /**
- * @private
- */
- function copyDeprecatedTopLevelAccessibilityOptions(chart) {
- deprecateFromOptionsMap(chart, ['accessibility'], ['accessibility'], {
- pointDateFormat: ['point', 'dateFormat'],
- pointDateFormatter: ['point', 'dateFormatter'],
- pointDescriptionFormatter: ['point', 'descriptionFormatter'],
- pointDescriptionThreshold: ['series',
- 'pointDescriptionEnabledThreshold'],
- pointNavigationThreshold: ['keyboardNavigation', 'seriesNavigation',
- 'pointNavigationEnabledThreshold'],
- pointValueDecimals: ['point', 'valueDecimals'],
- pointValuePrefix: ['point', 'valuePrefix'],
- pointValueSuffix: ['point', 'valueSuffix'],
- screenReaderSectionFormatter: ['screenReaderSection',
- 'beforeChartFormatter'],
- describeSingleSeries: ['series', 'describeSingleSeries'],
- seriesDescriptionFormatter: ['series', 'descriptionFormatter'],
- onTableAnchorClick: ['screenReaderSection', 'onViewDataTableClick'],
- axisRangeDateFormat: ['screenReaderSection', 'axisRangeDateFormat']
- });
- }
- /**
- * @private
- */
- function copyDeprecatedKeyboardNavigationOptions(chart) {
- deprecateFromOptionsMap(chart, ['accessibility', 'keyboardNavigation'], ['accessibility', 'keyboardNavigation', 'seriesNavigation'], {
- skipNullPoints: ['skipNullPoints'],
- mode: ['mode']
- });
- }
- /**
- * @private
- */
- function copyDeprecatedLangOptions(chart) {
- deprecateFromOptionsMap(chart, ['lang', 'accessibility'], ['lang', 'accessibility'], {
- legendItem: ['legend', 'legendItem'],
- legendLabel: ['legend', 'legendLabel'],
- mapZoomIn: ['zoom', 'mapZoomIn'],
- mapZoomOut: ['zoom', 'mapZoomOut'],
- resetZoomButton: ['zoom', 'resetZoomButton'],
- screenReaderRegionLabel: ['screenReaderSection',
- 'beforeRegionLabel'],
- rangeSelectorButton: ['rangeSelector', 'buttonText'],
- rangeSelectorMaxInput: ['rangeSelector', 'maxInputLabel'],
- rangeSelectorMinInput: ['rangeSelector', 'minInputLabel'],
- svgContainerEnd: ['screenReaderSection', 'endOfChartMarker'],
- viewAsDataTable: ['table', 'viewAsDataTableButtonText'],
- tableSummary: ['table', 'tableSummary']
- });
- }
- /**
- * Copy options that are deprecated over to new options. Logs warnings to
- * console if deprecated options are used.
- *
- * @private
- */
- function copyDeprecatedOptions(chart) {
- copyDeprecatedChartOptions(chart);
- copyDeprecatedAxisOptions(chart);
- if (chart.series) {
- copyDeprecatedSeriesOptions(chart);
- }
- copyDeprecatedTopLevelAccessibilityOptions(chart);
- copyDeprecatedKeyboardNavigationOptions(chart);
- copyDeprecatedLangOptions(chart);
- }
- return copyDeprecatedOptions;
- });
- _registerModule(_modules, 'Accessibility/A11yI18n.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Utilities.js']], function (Chart, H, F, U) {
- /* *
- *
- * Accessibility module - internationalization support
- *
- * (c) 2010-2021 Highsoft AS
- * Author: Øystein Moseng
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var format = F.format;
- var pick = U.pick;
- /* eslint-disable valid-jsdoc */
- /**
- * 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 = void 0;
- 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 = void 0;
- arr = ctx[arrayName];
- if (!isNaN(ix) && arr) {
- if (ix < 0) {
- val = arr[arr.length + ix];
- // Handle negative overshoot
- if (typeof val === 'undefined') {
- val = arr[0];
- }
- }
- else {
- val = arr[ix];
- // Handle positive overshoot
- if (typeof val === 'undefined') {
- val = arr[arr.length - 1];
- }
- }
- }
- return typeof val !== 'undefined' ? val : '';
- }
- // Standard substitution, delegate to 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.
- *
- * @requires modules/accessibility
- *
- * @function Highcharts.i18nFormat
- *
- * @param {string} formatString
- * The string to format.
- *
- * @param {Highcharts.Dictionary<*>} context
- * Context to apply to the format string.
- *
- * @param {Highcharts.Chart} chart
- * A `Chart` instance with a time object and numberFormatter, passed on
- * to format().
- *
- * @return {string}
- * The formatted string.
- */
- H.i18nFormat = function (formatString, context, chart) {
- 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 : cursor + 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 format below.
- tokens.forEach(function (token) {
- if (token.type === 'statement') {
- token.value = formatExtendedStatement(token.value, context);
- }
- });
- // Join string back together and pass to format to pick up non-array
- // statements.
- return format(tokens.reduce(function (acc, cur) {
- return acc + cur.value;
- }, ''), context, chart);
- };
- /**
- * Apply context to a format string from lang options of the chart.
- *
- * @requires modules/accessibility
- *
- * @function Highcharts.Chart#langFormat
- *
- * @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.
- */
- Chart.prototype.langFormat = function (langKey, context) {
- 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, this) : '';
- };
- });
- _registerModule(_modules, 'Accessibility/FocusBorder.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Renderer/SVG/SVGLabel.js'], _modules['Core/Utilities.js']], function (Chart, H, SVGElement, SVGLabel, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Extend SVG and Chart classes with focus border capabilities.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var addEvent = U.addEvent,
- extend = U.extend,
- pick = U.pick;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- // Attributes that trigger a focus border update
- var svgElementBorderUpdateTriggers = [
- 'x', 'y', 'transform', 'width', 'height', 'r', 'd', 'stroke-width'
- ];
- /**
- * Add hook to destroy focus border if SVG element is destroyed, unless
- * hook already exists.
- * @private
- * @param el Element to add destroy hook to
- */
- function addDestroyFocusBorderHook(el) {
- if (el.focusBorderDestroyHook) {
- return;
- }
- var origDestroy = el.destroy;
- el.destroy = function () {
- if (el.focusBorder && el.focusBorder.destroy) {
- el.focusBorder.destroy();
- }
- return origDestroy.apply(el, arguments);
- };
- el.focusBorderDestroyHook = origDestroy;
- }
- /**
- * Remove hook from SVG element added by addDestroyFocusBorderHook, if
- * existing.
- * @private
- * @param el Element to remove destroy hook from
- */
- function removeDestroyFocusBorderHook(el) {
- if (!el.focusBorderDestroyHook) {
- return;
- }
- el.destroy = el.focusBorderDestroyHook;
- delete el.focusBorderDestroyHook;
- }
- /**
- * Add hooks to update the focus border of an element when the element
- * size/position is updated, unless already added.
- * @private
- * @param el Element to add update hooks to
- * @param updateParams Parameters to pass through to addFocusBorder when updating.
- */
- function addUpdateFocusBorderHooks(el) {
- var updateParams = [];
- for (var _i = 1; _i < arguments.length; _i++) {
- updateParams[_i - 1] = arguments[_i];
- }
- if (el.focusBorderUpdateHooks) {
- return;
- }
- el.focusBorderUpdateHooks = {};
- svgElementBorderUpdateTriggers.forEach(function (trigger) {
- var setterKey = trigger + 'Setter';
- var origSetter = el[setterKey] || el._defaultSetter;
- el.focusBorderUpdateHooks[setterKey] = origSetter;
- el[setterKey] = function () {
- var ret = origSetter.apply(el,
- arguments);
- el.addFocusBorder.apply(el, updateParams);
- return ret;
- };
- });
- }
- /**
- * Remove hooks from SVG element added by addUpdateFocusBorderHooks, if
- * existing.
- * @private
- * @param el Element to remove update hooks from
- */
- function removeUpdateFocusBorderHooks(el) {
- if (!el.focusBorderUpdateHooks) {
- return;
- }
- Object.keys(el.focusBorderUpdateHooks).forEach(function (setterKey) {
- var origSetter = el.focusBorderUpdateHooks[setterKey];
- if (origSetter === el._defaultSetter) {
- delete el[setterKey];
- }
- else {
- el[setterKey] = origSetter;
- }
- });
- delete el.focusBorderUpdateHooks;
- }
- /*
- * Add focus border functionality to SVGElements. Draws a new rect on top of
- * element around its bounding box. This is used by multiple components.
- */
- extend(SVGElement.prototype, {
- /**
- * @private
- * @function Highcharts.SVGElement#addFocusBorder
- *
- * @param {number} margin
- *
- * @param {SVGAttributes} attribs
- */
- addFocusBorder: function (margin, attribs) {
- // Allow updating by just adding new border
- if (this.focusBorder) {
- this.removeFocusBorder();
- }
- // Add the border rect
- var bb = this.getBBox(),
- pad = pick(margin, 3);
- bb.x += this.translateX ? this.translateX : 0;
- bb.y += this.translateY ? this.translateY : 0;
- var borderPosX = bb.x - pad,
- borderPosY = bb.y - pad,
- borderWidth = bb.width + 2 * pad,
- borderHeight = bb.height + 2 * pad;
- // For text elements, apply x and y offset, #11397.
- /**
- * @private
- * @function
- *
- * @param {Highcharts.SVGElement} text
- *
- * @return {TextAnchorCorrectionObject}
- */
- function getTextAnchorCorrection(text) {
- var posXCorrection = 0,
- posYCorrection = 0;
- if (text.attr('text-anchor') === 'middle') {
- posXCorrection = H.isFirefox && text.rotation ? 0.25 : 0.5;
- posYCorrection = H.isFirefox && !text.rotation ? 0.75 : 0.5;
- }
- else if (!text.rotation) {
- posYCorrection = 0.75;
- }
- else {
- posXCorrection = 0.25;
- }
- return {
- x: posXCorrection,
- y: posYCorrection
- };
- }
- var isLabel = this instanceof SVGLabel;
- if (this.element.nodeName === 'text' || isLabel) {
- var isRotated = !!this.rotation;
- var correction = !isLabel ? getTextAnchorCorrection(this) :
- {
- x: isRotated ? 1 : 0,
- y: 0
- };
- var attrX = +this.attr('x');
- var attrY = +this.attr('y');
- if (!isNaN(attrX)) {
- borderPosX = attrX - (bb.width * correction.x) - pad;
- }
- if (!isNaN(attrY)) {
- borderPosY = attrY - (bb.height * correction.y) - pad;
- }
- if (isLabel && isRotated) {
- var temp = borderWidth;
- borderWidth = borderHeight;
- borderHeight = temp;
- if (!isNaN(attrX)) {
- borderPosX = attrX - (bb.height * correction.x) - pad;
- }
- if (!isNaN(attrY)) {
- borderPosY = attrY - (bb.width * correction.y) - pad;
- }
- }
- }
- this.focusBorder = this.renderer.rect(borderPosX, borderPosY, borderWidth, borderHeight, parseInt((attribs && attribs.r || 0).toString(), 10))
- .addClass('highcharts-focus-border')
- .attr({
- zIndex: 99
- })
- .add(this.parentGroup);
- if (!this.renderer.styledMode) {
- this.focusBorder.attr({
- stroke: attribs && attribs.stroke,
- 'stroke-width': attribs && attribs.strokeWidth
- });
- }
- addUpdateFocusBorderHooks(this, margin, attribs);
- addDestroyFocusBorderHook(this);
- },
- /**
- * @private
- * @function Highcharts.SVGElement#removeFocusBorder
- */
- removeFocusBorder: function () {
- removeUpdateFocusBorderHooks(this);
- removeDestroyFocusBorderHook(this);
- if (this.focusBorder) {
- this.focusBorder.destroy();
- delete this.focusBorder;
- }
- }
- });
- /**
- * Redraws the focus border on the currently focused element.
- *
- * @private
- * @function Highcharts.Chart#renderFocusBorder
- */
- Chart.prototype.renderFocusBorder = function () {
- var focusElement = this.focusElement,
- focusBorderOptions = this.options.accessibility.keyboardNavigation.focusBorder;
- if (focusElement) {
- focusElement.removeFocusBorder();
- if (focusBorderOptions.enabled) {
- focusElement.addFocusBorder(focusBorderOptions.margin, {
- stroke: focusBorderOptions.style.color,
- strokeWidth: focusBorderOptions.style.lineWidth,
- r: focusBorderOptions.style.borderRadius
- });
- }
- }
- };
- /**
- * Set chart's focus to an SVGElement. Calls focus() on it, and draws the focus
- * border. This is used by multiple components.
- *
- * @private
- * @function Highcharts.Chart#setFocusToElement
- *
- * @param {Highcharts.SVGElement} svgElement
- * Element to draw the border around.
- *
- * @param {SVGDOMElement|HTMLDOMElement} [focusElement]
- * If supplied, it draws the border around svgElement and sets the focus
- * to focusElement.
- */
- Chart.prototype.setFocusToElement = function (svgElement, focusElement) {
- var focusBorderOptions = this.options.accessibility.keyboardNavigation.focusBorder,
- browserFocusElement = focusElement || svgElement.element;
- // Set browser focus if possible
- if (browserFocusElement &&
- browserFocusElement.focus) {
- // If there is no focusin-listener, add one to work around Edge issue
- // where Narrator is not reading out points despite calling focus().
- if (!(browserFocusElement.hcEvents &&
- browserFocusElement.hcEvents.focusin)) {
- addEvent(browserFocusElement, 'focusin', function () { });
- }
- browserFocusElement.focus();
- // Hide default focus ring
- if (focusBorderOptions.hideBrowserFocusOutline) {
- browserFocusElement.style.outline = 'none';
- }
- }
- if (this.focusElement) {
- this.focusElement.removeFocusBorder();
- }
- this.focusElement = svgElement;
- this.renderFocusBorder();
- };
- });
- _registerModule(_modules, 'Accessibility/Accessibility.js', [_modules['Core/Chart/Chart.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Core/Globals.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Series/Point.js'], _modules['Core/Series/Series.js'], _modules['Core/Utilities.js'], _modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/KeyboardNavigation.js'], _modules['Accessibility/Components/LegendComponent.js'], _modules['Accessibility/Components/MenuComponent.js'], _modules['Accessibility/Components/SeriesComponent/SeriesComponent.js'], _modules['Accessibility/Components/ZoomComponent.js'], _modules['Accessibility/Components/RangeSelectorComponent.js'], _modules['Accessibility/Components/InfoRegionsComponent.js'], _modules['Accessibility/Components/ContainerComponent.js'], _modules['Accessibility/HighContrastMode.js'], _modules['Accessibility/HighContrastTheme.js'], _modules['Accessibility/Options/Options.js'], _modules['Accessibility/Options/LangOptions.js'], _modules['Accessibility/Options/DeprecatedOptions.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (Chart, ChartUtilities, H, KeyboardNavigationHandler, D, Point, Series, U, AccessibilityComponent, KeyboardNavigation, LegendComponent, MenuComponent, SeriesComponent, ZoomComponent, RangeSelectorComponent, InfoRegionsComponent, ContainerComponent, whcm, highContrastTheme, defaultOptionsA11Y, defaultLangOptions, copyDeprecatedOptions, HTMLUtilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Accessibility module for Highcharts
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var doc = H.doc;
- var defaultOptions = D.defaultOptions;
- var addEvent = U.addEvent,
- extend = U.extend,
- fireEvent = U.fireEvent,
- merge = U.merge;
- // Add default options
- merge(true, defaultOptions, defaultOptionsA11Y, {
- accessibility: {
- highContrastTheme: highContrastTheme
- },
- lang: defaultLangOptions
- });
- // Expose functionality on Highcharts namespace
- H.A11yChartUtilities = ChartUtilities;
- H.A11yHTMLUtilities = HTMLUtilities;
- H.KeyboardNavigationHandler = KeyboardNavigationHandler;
- H.AccessibilityComponent = AccessibilityComponent;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * The Accessibility class
- *
- * @private
- * @requires module:modules/accessibility
- *
- * @class
- * @name Highcharts.Accessibility
- *
- * @param {Highcharts.Chart} chart
- * Chart object
- */
- function Accessibility(chart) {
- this.init(chart);
- }
- Accessibility.prototype = {
- /**
- * Initialize the accessibility class
- * @private
- * @param {Highcharts.Chart} chart
- * Chart object
- */
- init: function (chart) {
- this.chart = chart;
- // Abort on old browsers
- if (!doc.addEventListener || !chart.renderer.isSVG) {
- chart.renderTo.setAttribute('aria-hidden', true);
- return;
- }
- // Copy over any deprecated options that are used. We could do this on
- // every update, but it is probably not needed.
- copyDeprecatedOptions(chart);
- this.initComponents();
- this.keyboardNavigation = new KeyboardNavigation(chart, this.components);
- this.update();
- },
- /**
- * @private
- */
- initComponents: function () {
- var chart = this.chart,
- a11yOptions = chart.options.accessibility;
- this.components = {
- container: new ContainerComponent(),
- infoRegions: new InfoRegionsComponent(),
- legend: new LegendComponent(),
- chartMenu: new MenuComponent(),
- rangeSelector: new RangeSelectorComponent(),
- series: new SeriesComponent(),
- zoom: new ZoomComponent()
- };
- if (a11yOptions.customComponents) {
- extend(this.components, a11yOptions.customComponents);
- }
- var components = this.components;
- this.getComponentOrder().forEach(function (componentName) {
- components[componentName].initBase(chart);
- components[componentName].init();
- });
- },
- /**
- * Get order to update components in.
- * @private
- */
- getComponentOrder: function () {
- if (!this.components) {
- return []; // For zombie accessibility object on old browsers
- }
- if (!this.components.series) {
- return Object.keys(this.components);
- }
- var componentsExceptSeries = Object.keys(this.components)
- .filter(function (c) { return c !== 'series'; });
- // Update series first, so that other components can read accessibility
- // info on points.
- return ['series'].concat(componentsExceptSeries);
- },
- /**
- * Update all components.
- */
- update: function () {
- var components = this.components,
- chart = this.chart,
- a11yOptions = chart.options.accessibility;
- fireEvent(chart, 'beforeA11yUpdate');
- // Update the chart type list as this is used by multiple modules
- chart.types = this.getChartTypes();
- // Update markup
- this.getComponentOrder().forEach(function (componentName) {
- components[componentName].onChartUpdate();
- fireEvent(chart, 'afterA11yComponentUpdate', {
- name: componentName,
- component: components[componentName]
- });
- });
- // Update keyboard navigation
- this.keyboardNavigation.update(a11yOptions.keyboardNavigation.order);
- // Handle high contrast mode
- if (!chart.highContrastModeActive && // Only do this once
- whcm.isHighContrastModeActive()) {
- whcm.setHighContrastTheme(chart);
- }
- fireEvent(chart, 'afterA11yUpdate', {
- accessibility: this
- });
- },
- /**
- * Destroy all elements.
- */
- destroy: function () {
- var chart = this.chart || {};
- // Destroy components
- var components = this.components;
- Object.keys(components).forEach(function (componentName) {
- components[componentName].destroy();
- components[componentName].destroyBase();
- });
- // Kill keyboard nav
- if (this.keyboardNavigation) {
- this.keyboardNavigation.destroy();
- }
- // Hide container from screen readers if it exists
- if (chart.renderTo) {
- chart.renderTo.setAttribute('aria-hidden', true);
- }
- // Remove focus border if it exists
- if (chart.focusElement) {
- chart.focusElement.removeFocusBorder();
- }
- },
- /**
- * Return a list of the types of series we have in the chart.
- * @private
- */
- getChartTypes: function () {
- var types = {};
- this.chart.series.forEach(function (series) {
- types[series.type] = 1;
- });
- return Object.keys(types);
- }
- };
- /**
- * @private
- */
- Chart.prototype.updateA11yEnabled = function () {
- var a11y = this.accessibility,
- accessibilityOptions = this.options.accessibility;
- if (accessibilityOptions && accessibilityOptions.enabled) {
- if (a11y) {
- a11y.update();
- }
- else {
- this.accessibility = a11y = new Accessibility(this);
- }
- }
- else if (a11y) {
- // Destroy if after update we have a11y and it is disabled
- if (a11y.destroy) {
- a11y.destroy();
- }
- delete this.accessibility;
- }
- else {
- // Just hide container
- this.renderTo.setAttribute('aria-hidden', true);
- }
- };
- // Handle updates to the module and send render updates to components
- addEvent(Chart, 'render', function (e) {
- // Update/destroy
- if (this.a11yDirty && this.renderTo) {
- delete this.a11yDirty;
- this.updateA11yEnabled();
- }
- var a11y = this.accessibility;
- if (a11y) {
- a11y.getComponentOrder().forEach(function (componentName) {
- a11y.components[componentName].onChartRender();
- });
- }
- });
- // Update with chart/series/point updates
- addEvent(Chart, 'update', function (e) {
- // Merge new options
- var newOptions = e.options.accessibility;
- if (newOptions) {
- // Handle custom component updating specifically
- if (newOptions.customComponents) {
- this.options.accessibility.customComponents =
- newOptions.customComponents;
- delete newOptions.customComponents;
- }
- merge(true, this.options.accessibility, newOptions);
- // Recreate from scratch
- if (this.accessibility && this.accessibility.destroy) {
- this.accessibility.destroy();
- delete this.accessibility;
- }
- }
- // Mark dirty for update
- this.a11yDirty = true;
- });
- // Mark dirty for update
- addEvent(Point, 'update', function () {
- if (this.series.chart.accessibility) {
- this.series.chart.a11yDirty = true;
- }
- });
- ['addSeries', 'init'].forEach(function (event) {
- addEvent(Chart, event, function () {
- this.a11yDirty = true;
- });
- });
- ['update', 'updatedData', 'remove'].forEach(function (event) {
- addEvent(Series, event, function () {
- if (this.chart.accessibility) {
- this.chart.a11yDirty = true;
- }
- });
- });
- // Direct updates (events happen after render)
- [
- 'afterDrilldown', 'drillupall'
- ].forEach(function (event) {
- addEvent(Chart, event, function () {
- if (this.accessibility) {
- this.accessibility.update();
- }
- });
- });
- // Destroy with chart
- addEvent(Chart, 'destroy', function () {
- if (this.accessibility) {
- this.accessibility.destroy();
- }
- });
- });
- _registerModule(_modules, 'masters/modules/accessibility.src.js', [], function () {
- });
- }));
|