highstock.src.js 1.2 MB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008
  1. /**
  2. * @license Highstock JS v5.0.12 (2017-05-24)
  3. *
  4. * (c) 2009-2016 Torstein Honsi
  5. *
  6. * License: www.highcharts.com/license
  7. */
  8. 'use strict';
  9. (function(root, factory) {
  10. if (typeof module === 'object' && module.exports) {
  11. module.exports = root.document ?
  12. factory(root) :
  13. factory;
  14. } else {
  15. root.Highcharts = factory(root);
  16. }
  17. }(typeof window !== 'undefined' ? window : this, function(win) {
  18. var Highcharts = (function() {
  19. /**
  20. * (c) 2010-2017 Torstein Honsi
  21. *
  22. * License: www.highcharts.com/license
  23. */
  24. /* global window */
  25. var win = window,
  26. doc = win.document;
  27. var SVG_NS = 'http://www.w3.org/2000/svg',
  28. userAgent = (win.navigator && win.navigator.userAgent) || '',
  29. svg = doc && doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
  30. isMS = /(edge|msie|trident)/i.test(userAgent) && !window.opera,
  31. vml = !svg,
  32. isFirefox = /Firefox/.test(userAgent),
  33. hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4; // issue #38
  34. var Highcharts = win.Highcharts ? win.Highcharts.error(16, true) : {
  35. product: 'Highstock',
  36. version: '5.0.12',
  37. deg2rad: Math.PI * 2 / 360,
  38. doc: doc,
  39. hasBidiBug: hasBidiBug,
  40. hasTouch: doc && doc.documentElement.ontouchstart !== undefined,
  41. isMS: isMS,
  42. isWebKit: /AppleWebKit/.test(userAgent),
  43. isFirefox: isFirefox,
  44. isTouchDevice: /(Mobile|Android|Windows Phone)/.test(userAgent),
  45. SVG_NS: SVG_NS,
  46. chartCount: 0,
  47. seriesTypes: {},
  48. symbolSizes: {},
  49. svg: svg,
  50. vml: vml,
  51. win: win,
  52. marginNames: ['plotTop', 'marginRight', 'marginBottom', 'plotLeft'],
  53. noop: function() {
  54. return undefined;
  55. },
  56. /**
  57. * An array containing the current chart objects in the page. A chart's
  58. * position in the array is preserved throughout the page's lifetime. When
  59. * a chart is destroyed, the array item becomes `undefined`.
  60. * @type {Array.<Highcharts.Chart>}
  61. * @memberOf Highcharts
  62. */
  63. charts: []
  64. };
  65. return Highcharts;
  66. }());
  67. (function(H) {
  68. /**
  69. * (c) 2010-2017 Torstein Honsi
  70. *
  71. * License: www.highcharts.com/license
  72. */
  73. /* eslint max-len: ["warn", 80, 4] */
  74. /**
  75. * The Highcharts object is the placeholder for all other members, and various
  76. * utility functions. The most important member of the namespace would be the
  77. * chart constructor.
  78. *
  79. * @example
  80. * var chart = Highcharts.chart('container', { ... });
  81. *
  82. * @namespace Highcharts
  83. */
  84. var timers = [];
  85. var charts = H.charts,
  86. doc = H.doc,
  87. win = H.win;
  88. /**
  89. * Provide error messages for debugging, with links to online explanation. This
  90. * function can be overridden to provide custom error handling.
  91. *
  92. * @function #error
  93. * @memberOf Highcharts
  94. * @param {Number|String} code - The error code. See [errors.xml]{@link
  95. * https://github.com/highcharts/highcharts/blob/master/errors/errors.xml}
  96. * for available codes. If it is a string, the error message is printed
  97. * directly in the console.
  98. * @param {Boolean} [stop=false] - Whether to throw an error or just log a
  99. * warning in the console.
  100. *
  101. * @sample highcharts/chart/highcharts-error/ Custom error handler
  102. */
  103. H.error = function(code, stop) {
  104. var msg = H.isNumber(code) ?
  105. 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code :
  106. code;
  107. if (stop) {
  108. throw new Error(msg);
  109. }
  110. // else ...
  111. if (win.console) {
  112. console.log(msg); // eslint-disable-line no-console
  113. }
  114. };
  115. /**
  116. * An animator object used internally. One instance applies to one property
  117. * (attribute or style prop) on one element. Animation is always initiated
  118. * through {@link SVGElement#animate}.
  119. *
  120. * @constructor Fx
  121. * @memberOf Highcharts
  122. * @param {HTMLDOMElement|SVGElement} elem - The element to animate.
  123. * @param {AnimationOptions} options - Animation options.
  124. * @param {string} prop - The single attribute or CSS property to animate.
  125. * @private
  126. *
  127. * @example
  128. * var rect = renderer.rect(0, 0, 10, 10).add();
  129. * rect.animate({ width: 100 });
  130. */
  131. H.Fx = function(elem, options, prop) {
  132. this.options = options;
  133. this.elem = elem;
  134. this.prop = prop;
  135. };
  136. H.Fx.prototype = {
  137. /**
  138. * Set the current step of a path definition on SVGElement.
  139. *
  140. * @function #dSetter
  141. * @memberOf Highcharts.Fx
  142. */
  143. dSetter: function() {
  144. var start = this.paths[0],
  145. end = this.paths[1],
  146. ret = [],
  147. now = this.now,
  148. i = start.length,
  149. startVal;
  150. // Land on the final path without adjustment points appended in the ends
  151. if (now === 1) {
  152. ret = this.toD;
  153. } else if (i === end.length && now < 1) {
  154. while (i--) {
  155. startVal = parseFloat(start[i]);
  156. ret[i] =
  157. isNaN(startVal) ? // a letter instruction like M or L
  158. start[i] :
  159. now * (parseFloat(end[i] - startVal)) + startVal;
  160. }
  161. // If animation is finished or length not matching, land on right value
  162. } else {
  163. ret = end;
  164. }
  165. this.elem.attr('d', ret, null, true);
  166. },
  167. /**
  168. * Update the element with the current animation step.
  169. *
  170. * @function #update
  171. * @memberOf Highcharts.Fx
  172. */
  173. update: function() {
  174. var elem = this.elem,
  175. prop = this.prop, // if destroyed, it is null
  176. now = this.now,
  177. step = this.options.step;
  178. // Animation setter defined from outside
  179. if (this[prop + 'Setter']) {
  180. this[prop + 'Setter']();
  181. // Other animations on SVGElement
  182. } else if (elem.attr) {
  183. if (elem.element) {
  184. elem.attr(prop, now, null, true);
  185. }
  186. // HTML styles, raw HTML content like container size
  187. } else {
  188. elem.style[prop] = now + this.unit;
  189. }
  190. if (step) {
  191. step.call(elem, now, this);
  192. }
  193. },
  194. /**
  195. * Run an animation.
  196. *
  197. * @function #run
  198. * @memberOf Highcharts.Fx
  199. * @param {Number} from - The current value, value to start from.
  200. * @param {Number} to - The end value, value to land on.
  201. * @param {String} [unit] - The property unit, for example `px`.
  202. * @returns {void}
  203. */
  204. run: function(from, to, unit) {
  205. var self = this,
  206. timer = function(gotoEnd) {
  207. return timer.stopped ? false : self.step(gotoEnd);
  208. },
  209. i;
  210. this.startTime = +new Date();
  211. this.start = from;
  212. this.end = to;
  213. this.unit = unit;
  214. this.now = this.start;
  215. this.pos = 0;
  216. timer.elem = this.elem;
  217. timer.prop = this.prop;
  218. if (timer() && timers.push(timer) === 1) {
  219. timer.timerId = setInterval(function() {
  220. for (i = 0; i < timers.length; i++) {
  221. if (!timers[i]()) {
  222. timers.splice(i--, 1);
  223. }
  224. }
  225. if (!timers.length) {
  226. clearInterval(timer.timerId);
  227. }
  228. }, 13);
  229. }
  230. },
  231. /**
  232. * Run a single step in the animation.
  233. *
  234. * @function #step
  235. * @memberOf Highcharts.Fx
  236. * @param {Boolean} [gotoEnd] - Whether to go to the endpoint of the
  237. * animation after abort.
  238. * @returns {Boolean} Returns `true` if animation continues.
  239. */
  240. step: function(gotoEnd) {
  241. var t = +new Date(),
  242. ret,
  243. done,
  244. options = this.options,
  245. elem = this.elem,
  246. complete = options.complete,
  247. duration = options.duration,
  248. curAnim = options.curAnim;
  249. if (elem.attr && !elem.element) { // #2616, element is destroyed
  250. ret = false;
  251. } else if (gotoEnd || t >= duration + this.startTime) {
  252. this.now = this.end;
  253. this.pos = 1;
  254. this.update();
  255. curAnim[this.prop] = true;
  256. done = true;
  257. H.objectEach(curAnim, function(val) {
  258. if (val !== true) {
  259. done = false;
  260. }
  261. });
  262. if (done && complete) {
  263. complete.call(elem);
  264. }
  265. ret = false;
  266. } else {
  267. this.pos = options.easing((t - this.startTime) / duration);
  268. this.now = this.start + ((this.end - this.start) * this.pos);
  269. this.update();
  270. ret = true;
  271. }
  272. return ret;
  273. },
  274. /**
  275. * Prepare start and end values so that the path can be animated one to one.
  276. *
  277. * @function #initPath
  278. * @memberOf Highcharts.Fx
  279. * @param {SVGElement} elem - The SVGElement item.
  280. * @param {String} fromD - Starting path definition.
  281. * @param {Array} toD - Ending path definition.
  282. * @returns {Array} An array containing start and end paths in array form
  283. * so that they can be animated in parallel.
  284. */
  285. initPath: function(elem, fromD, toD) {
  286. fromD = fromD || '';
  287. var shift,
  288. startX = elem.startX,
  289. endX = elem.endX,
  290. bezier = fromD.indexOf('C') > -1,
  291. numParams = bezier ? 7 : 3,
  292. fullLength,
  293. slice,
  294. i,
  295. start = fromD.split(' '),
  296. end = toD.slice(), // copy
  297. isArea = elem.isArea,
  298. positionFactor = isArea ? 2 : 1,
  299. reverse;
  300. /**
  301. * In splines make moveTo and lineTo points have six parameters like
  302. * bezier curves, to allow animation one-to-one.
  303. */
  304. function sixify(arr) {
  305. var isOperator,
  306. nextIsOperator;
  307. i = arr.length;
  308. while (i--) {
  309. // Fill in dummy coordinates only if the next operator comes
  310. // three places behind (#5788)
  311. isOperator = arr[i] === 'M' || arr[i] === 'L';
  312. nextIsOperator = /[a-zA-Z]/.test(arr[i + 3]);
  313. if (isOperator && nextIsOperator) {
  314. arr.splice(
  315. i + 1, 0,
  316. arr[i + 1], arr[i + 2],
  317. arr[i + 1], arr[i + 2]
  318. );
  319. }
  320. }
  321. }
  322. /**
  323. * Insert an array at the given position of another array
  324. */
  325. function insertSlice(arr, subArr, index) {
  326. [].splice.apply(
  327. arr, [index, 0].concat(subArr)
  328. );
  329. }
  330. /**
  331. * If shifting points, prepend a dummy point to the end path.
  332. */
  333. function prepend(arr, other) {
  334. while (arr.length < fullLength) {
  335. // Move to, line to or curve to?
  336. arr[0] = other[fullLength - arr.length];
  337. // Prepend a copy of the first point
  338. insertSlice(arr, arr.slice(0, numParams), 0);
  339. // For areas, the bottom path goes back again to the left, so we
  340. // need to append a copy of the last point.
  341. if (isArea) {
  342. insertSlice(
  343. arr,
  344. arr.slice(arr.length - numParams), arr.length
  345. );
  346. i--;
  347. }
  348. }
  349. arr[0] = 'M';
  350. }
  351. /**
  352. * Copy and append last point until the length matches the end length
  353. */
  354. function append(arr, other) {
  355. var i = (fullLength - arr.length) / numParams;
  356. while (i > 0 && i--) {
  357. // Pull out the slice that is going to be appended or inserted.
  358. // In a line graph, the positionFactor is 1, and the last point
  359. // is sliced out. In an area graph, the positionFactor is 2,
  360. // causing the middle two points to be sliced out, since an area
  361. // path starts at left, follows the upper path then turns and
  362. // follows the bottom back.
  363. slice = arr.slice().splice(
  364. (arr.length / positionFactor) - numParams,
  365. numParams * positionFactor
  366. );
  367. // Move to, line to or curve to?
  368. slice[0] = other[fullLength - numParams - (i * numParams)];
  369. // Disable first control point
  370. if (bezier) {
  371. slice[numParams - 6] = slice[numParams - 2];
  372. slice[numParams - 5] = slice[numParams - 1];
  373. }
  374. // Now insert the slice, either in the middle (for areas) or at
  375. // the end (for lines)
  376. insertSlice(arr, slice, arr.length / positionFactor);
  377. if (isArea) {
  378. i--;
  379. }
  380. }
  381. }
  382. if (bezier) {
  383. sixify(start);
  384. sixify(end);
  385. }
  386. // For sideways animation, find out how much we need to shift to get the
  387. // start path Xs to match the end path Xs.
  388. if (startX && endX) {
  389. for (i = 0; i < startX.length; i++) {
  390. // Moving left, new points coming in on right
  391. if (startX[i] === endX[0]) {
  392. shift = i;
  393. break;
  394. // Moving right
  395. } else if (startX[0] ===
  396. endX[endX.length - startX.length + i]) {
  397. shift = i;
  398. reverse = true;
  399. break;
  400. }
  401. }
  402. if (shift === undefined) {
  403. start = [];
  404. }
  405. }
  406. if (start.length && H.isNumber(shift)) {
  407. // The common target length for the start and end array, where both
  408. // arrays are padded in opposite ends
  409. fullLength = end.length + shift * positionFactor * numParams;
  410. if (!reverse) {
  411. prepend(end, start);
  412. append(start, end);
  413. } else {
  414. prepend(start, end);
  415. append(end, start);
  416. }
  417. }
  418. return [start, end];
  419. }
  420. }; // End of Fx prototype
  421. /**
  422. * Handle animation of the color attributes directly.
  423. */
  424. H.Fx.prototype.fillSetter =
  425. H.Fx.prototype.strokeSetter = function() {
  426. this.elem.attr(
  427. this.prop,
  428. H.color(this.start).tweenTo(H.color(this.end), this.pos),
  429. null,
  430. true
  431. );
  432. };
  433. /**
  434. * Utility function to extend an object with the members of another.
  435. *
  436. * @function #extend
  437. * @memberOf Highcharts
  438. * @param {Object} a - The object to be extended.
  439. * @param {Object} b - The object to add to the first one.
  440. * @returns {Object} Object a, the original object.
  441. */
  442. H.extend = function(a, b) {
  443. var n;
  444. if (!a) {
  445. a = {};
  446. }
  447. for (n in b) {
  448. a[n] = b[n];
  449. }
  450. return a;
  451. };
  452. /**
  453. * Utility function to deep merge two or more objects and return a third object.
  454. * If the first argument is true, the contents of the second object is copied
  455. * into the first object. The merge function can also be used with a single
  456. * object argument to create a deep copy of an object.
  457. *
  458. * @function #merge
  459. * @memberOf Highcharts
  460. * @param {Boolean} [extend] - Whether to extend the left-side object (a) or
  461. return a whole new object.
  462. * @param {Object} a - The first object to extend. When only this is given, the
  463. function returns a deep copy.
  464. * @param {...Object} [n] - An object to merge into the previous one.
  465. * @returns {Object} - The merged object. If the first argument is true, the
  466. * return is the same as the second argument.
  467. */
  468. H.merge = function() {
  469. var i,
  470. args = arguments,
  471. len,
  472. ret = {},
  473. doCopy = function(copy, original) {
  474. // An object is replacing a primitive
  475. if (typeof copy !== 'object') {
  476. copy = {};
  477. }
  478. H.objectEach(original, function(value, key) {
  479. // Copy the contents of objects, but not arrays or DOM nodes
  480. if (
  481. H.isObject(value, true) &&
  482. !H.isClass(value) &&
  483. !H.isDOMElement(value)
  484. ) {
  485. copy[key] = doCopy(copy[key] || {}, value);
  486. // Primitives and arrays are copied over directly
  487. } else {
  488. copy[key] = original[key];
  489. }
  490. });
  491. return copy;
  492. };
  493. // If first argument is true, copy into the existing object. Used in
  494. // setOptions.
  495. if (args[0] === true) {
  496. ret = args[1];
  497. args = Array.prototype.slice.call(args, 2);
  498. }
  499. // For each argument, extend the return
  500. len = args.length;
  501. for (i = 0; i < len; i++) {
  502. ret = doCopy(ret, args[i]);
  503. }
  504. return ret;
  505. };
  506. /**
  507. * Shortcut for parseInt
  508. * @ignore
  509. * @param {Object} s
  510. * @param {Number} mag Magnitude
  511. */
  512. H.pInt = function(s, mag) {
  513. return parseInt(s, mag || 10);
  514. };
  515. /**
  516. * Utility function to check for string type.
  517. *
  518. * @function #isString
  519. * @memberOf Highcharts
  520. * @param {Object} s - The item to check.
  521. * @returns {Boolean} - True if the argument is a string.
  522. */
  523. H.isString = function(s) {
  524. return typeof s === 'string';
  525. };
  526. /**
  527. * Utility function to check if an item is an array.
  528. *
  529. * @function #isArray
  530. * @memberOf Highcharts
  531. * @param {Object} obj - The item to check.
  532. * @returns {Boolean} - True if the argument is an array.
  533. */
  534. H.isArray = function(obj) {
  535. var str = Object.prototype.toString.call(obj);
  536. return str === '[object Array]' || str === '[object Array Iterator]';
  537. };
  538. /**
  539. * Utility function to check if an item is of type object.
  540. *
  541. * @function #isObject
  542. * @memberOf Highcharts
  543. * @param {Object} obj - The item to check.
  544. * @param {Boolean} [strict=false] - Also checks that the object is not an
  545. * array.
  546. * @returns {Boolean} - True if the argument is an object.
  547. */
  548. H.isObject = function(obj, strict) {
  549. return !!obj && typeof obj === 'object' && (!strict || !H.isArray(obj));
  550. };
  551. /**
  552. * Utility function to check if an Object is a HTML Element.
  553. *
  554. * @function #isDOMElement
  555. * @memberOf Highcharts
  556. * @param {Object} obj - The item to check.
  557. * @returns {Boolean} - True if the argument is a HTML Element.
  558. */
  559. H.isDOMElement = function(obj) {
  560. return H.isObject(obj) && typeof obj.nodeType === 'number';
  561. };
  562. /**
  563. * Utility function to check if an Object is an class.
  564. *
  565. * @function #isClass
  566. * @memberOf Highcharts
  567. * @param {Object} obj - The item to check.
  568. * @returns {Boolean} - True if the argument is an class.
  569. */
  570. H.isClass = function(obj) {
  571. var c = obj && obj.constructor;
  572. return !!(
  573. H.isObject(obj, true) &&
  574. !H.isDOMElement(obj) &&
  575. (c && c.name && c.name !== 'Object')
  576. );
  577. };
  578. /**
  579. * Utility function to check if an item is of type number.
  580. *
  581. * @function #isNumber
  582. * @memberOf Highcharts
  583. * @param {Object} n - The item to check.
  584. * @returns {Boolean} - True if the item is a number and is not NaN.
  585. */
  586. H.isNumber = function(n) {
  587. return typeof n === 'number' && !isNaN(n);
  588. };
  589. /**
  590. * Remove the last occurence of an item from an array.
  591. *
  592. * @function #erase
  593. * @memberOf Highcharts
  594. * @param {Array} arr - The array.
  595. * @param {*} item - The item to remove.
  596. */
  597. H.erase = function(arr, item) {
  598. var i = arr.length;
  599. while (i--) {
  600. if (arr[i] === item) {
  601. arr.splice(i, 1);
  602. break;
  603. }
  604. }
  605. };
  606. /**
  607. * Check if an object is null or undefined.
  608. *
  609. * @function #defined
  610. * @memberOf Highcharts
  611. * @param {Object} obj - The object to check.
  612. * @returns {Boolean} - False if the object is null or undefined, otherwise
  613. * true.
  614. */
  615. H.defined = function(obj) {
  616. return obj !== undefined && obj !== null;
  617. };
  618. /**
  619. * Set or get an attribute or an object of attributes. To use as a setter, pass
  620. * a key and a value, or let the second argument be a collection of keys and
  621. * values. To use as a getter, pass only a string as the second argument.
  622. *
  623. * @function #attr
  624. * @memberOf Highcharts
  625. * @param {Object} elem - The DOM element to receive the attribute(s).
  626. * @param {String|Object} [prop] - The property or an object of key-value pairs.
  627. * @param {String} [value] - The value if a single property is set.
  628. * @returns {*} When used as a getter, return the value.
  629. */
  630. H.attr = function(elem, prop, value) {
  631. var ret;
  632. // if the prop is a string
  633. if (H.isString(prop)) {
  634. // set the value
  635. if (H.defined(value)) {
  636. elem.setAttribute(prop, value);
  637. // get the value
  638. } else if (elem && elem.getAttribute) {
  639. ret = elem.getAttribute(prop);
  640. }
  641. // else if prop is defined, it is a hash of key/value pairs
  642. } else if (H.defined(prop) && H.isObject(prop)) {
  643. H.objectEach(prop, function(val, key) {
  644. elem.setAttribute(key, val);
  645. });
  646. }
  647. return ret;
  648. };
  649. /**
  650. * Check if an element is an array, and if not, make it into an array.
  651. *
  652. * @function #splat
  653. * @memberOf Highcharts
  654. * @param obj {*} - The object to splat.
  655. * @returns {Array} The produced or original array.
  656. */
  657. H.splat = function(obj) {
  658. return H.isArray(obj) ? obj : [obj];
  659. };
  660. /**
  661. * Set a timeout if the delay is given, otherwise perform the function
  662. * synchronously.
  663. *
  664. * @function #syncTimeout
  665. * @memberOf Highcharts
  666. * @param {Function} fn - The function callback.
  667. * @param {Number} delay - Delay in milliseconds.
  668. * @param {Object} [context] - The context.
  669. * @returns {Number} An identifier for the timeout that can later be cleared
  670. * with clearTimeout.
  671. */
  672. H.syncTimeout = function(fn, delay, context) {
  673. if (delay) {
  674. return setTimeout(fn, delay, context);
  675. }
  676. fn.call(0, context);
  677. };
  678. /**
  679. * Return the first value that is not null or undefined.
  680. *
  681. * @function #pick
  682. * @memberOf Highcharts
  683. * @param {...*} items - Variable number of arguments to inspect.
  684. * @returns {*} The value of the first argument that is not null or undefined.
  685. */
  686. H.pick = function() {
  687. var args = arguments,
  688. i,
  689. arg,
  690. length = args.length;
  691. for (i = 0; i < length; i++) {
  692. arg = args[i];
  693. if (arg !== undefined && arg !== null) {
  694. return arg;
  695. }
  696. }
  697. };
  698. /**
  699. * @typedef {Object} CSSObject - A style object with camel case property names.
  700. * The properties can be whatever styles are supported on the given SVG or HTML
  701. * element.
  702. * @example
  703. * {
  704. * fontFamily: 'monospace',
  705. * fontSize: '1.2em'
  706. * }
  707. */
  708. /**
  709. * Set CSS on a given element.
  710. *
  711. * @function #css
  712. * @memberOf Highcharts
  713. * @param {HTMLDOMElement} el - A HTML DOM element.
  714. * @param {CSSObject} styles - Style object with camel case property names.
  715. * @returns {void}
  716. */
  717. H.css = function(el, styles) {
  718. if (H.isMS && !H.svg) { // #2686
  719. if (styles && styles.opacity !== undefined) {
  720. styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
  721. }
  722. }
  723. H.extend(el.style, styles);
  724. };
  725. /**
  726. * A HTML DOM element.
  727. * @typedef {Object} HTMLDOMElement
  728. */
  729. /**
  730. * Utility function to create an HTML element with attributes and styles.
  731. *
  732. * @function #createElement
  733. * @memberOf Highcharts
  734. * @param {String} tag - The HTML tag.
  735. * @param {Object} [attribs] - Attributes as an object of key-value pairs.
  736. * @param {CSSObject} [styles] - Styles as an object of key-value pairs.
  737. * @param {Object} [parent] - The parent HTML object.
  738. * @param {Boolean} [nopad=false] - If true, remove all padding, border and
  739. * margin.
  740. * @returns {HTMLDOMElement} The created DOM element.
  741. */
  742. H.createElement = function(tag, attribs, styles, parent, nopad) {
  743. var el = doc.createElement(tag),
  744. css = H.css;
  745. if (attribs) {
  746. H.extend(el, attribs);
  747. }
  748. if (nopad) {
  749. css(el, {
  750. padding: 0,
  751. border: 'none',
  752. margin: 0
  753. });
  754. }
  755. if (styles) {
  756. css(el, styles);
  757. }
  758. if (parent) {
  759. parent.appendChild(el);
  760. }
  761. return el;
  762. };
  763. /**
  764. * Extend a prototyped class by new members.
  765. *
  766. * @function #extendClass
  767. * @memberOf Highcharts
  768. * @param {Object} parent - The parent prototype to inherit.
  769. * @param {Object} members - A collection of prototype members to add or
  770. * override compared to the parent prototype.
  771. * @returns {Object} A new prototype.
  772. */
  773. H.extendClass = function(parent, members) {
  774. var object = function() {};
  775. object.prototype = new parent(); // eslint-disable-line new-cap
  776. H.extend(object.prototype, members);
  777. return object;
  778. };
  779. /**
  780. * Left-pad a string to a given length by adding a character repetetively.
  781. *
  782. * @function #pad
  783. * @memberOf Highcharts
  784. * @param {Number} number - The input string or number.
  785. * @param {Number} length - The desired string length.
  786. * @param {String} [padder=0] - The character to pad with.
  787. * @returns {String} The padded string.
  788. */
  789. H.pad = function(number, length, padder) {
  790. return new Array((length || 2) + 1 -
  791. String(number).length).join(padder || 0) + number;
  792. };
  793. /**
  794. * @typedef {Number|String} RelativeSize - If a number is given, it defines the
  795. * pixel length. If a percentage string is given, like for example `'50%'`,
  796. * the setting defines a length relative to a base size, for example the size
  797. * of a container.
  798. */
  799. /**
  800. * Return a length based on either the integer value, or a percentage of a base.
  801. *
  802. * @function #relativeLength
  803. * @memberOf Highcharts
  804. * @param {RelativeSize} value - A percentage string or a number.
  805. * @param {Number} base - The full length that represents 100%.
  806. * @returns {Number} The computed length.
  807. */
  808. H.relativeLength = function(value, base) {
  809. return (/%$/).test(value) ?
  810. base * parseFloat(value) / 100 :
  811. parseFloat(value);
  812. };
  813. /**
  814. * Wrap a method with extended functionality, preserving the original function.
  815. *
  816. * @function #wrap
  817. * @memberOf Highcharts
  818. * @param {Object} obj - The context object that the method belongs to. In real
  819. * cases, this is often a prototype.
  820. * @param {String} method - The name of the method to extend.
  821. * @param {Function} func - A wrapper function callback. This function is called
  822. * with the same arguments as the original function, except that the
  823. * original function is unshifted and passed as the first argument.
  824. * @returns {void}
  825. */
  826. H.wrap = function(obj, method, func) {
  827. var proceed = obj[method];
  828. obj[method] = function() {
  829. var args = Array.prototype.slice.call(arguments),
  830. outerArgs = arguments,
  831. ctx = this,
  832. ret;
  833. ctx.proceed = function() {
  834. proceed.apply(ctx, arguments.length ? arguments : outerArgs);
  835. };
  836. args.unshift(proceed);
  837. ret = func.apply(this, args);
  838. ctx.proceed = null;
  839. return ret;
  840. };
  841. };
  842. /**
  843. * Get the time zone offset based on the current timezone information as set in
  844. * the global options.
  845. *
  846. * @function #getTZOffset
  847. * @memberOf Highcharts
  848. * @param {Number} timestamp - The JavaScript timestamp to inspect.
  849. * @return {Number} - The timezone offset in minutes compared to UTC.
  850. */
  851. H.getTZOffset = function(timestamp) {
  852. var d = H.Date;
  853. return ((d.hcGetTimezoneOffset && d.hcGetTimezoneOffset(timestamp)) ||
  854. d.hcTimezoneOffset || 0) * 60000;
  855. };
  856. /**
  857. * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970) into a
  858. * human readable date string. The format is a subset of the formats for PHP's
  859. * [strftime]{@link
  860. * http://www.php.net/manual/en/function.strftime.php} function. Additional
  861. * formats can be given in the {@link Highcharts.dateFormats} hook.
  862. *
  863. * @function #dateFormat
  864. * @memberOf Highcharts
  865. * @param {String} format - The desired format where various time
  866. * representations are prefixed with %.
  867. * @param {Number} timestamp - The JavaScript timestamp.
  868. * @param {Boolean} [capitalize=false] - Upper case first letter in the return.
  869. * @returns {String} The formatted date.
  870. */
  871. H.dateFormat = function(format, timestamp, capitalize) {
  872. if (!H.defined(timestamp) || isNaN(timestamp)) {
  873. return H.defaultOptions.lang.invalidDate || '';
  874. }
  875. format = H.pick(format, '%Y-%m-%d %H:%M:%S');
  876. var D = H.Date,
  877. date = new D(timestamp - H.getTZOffset(timestamp)),
  878. // get the basic time values
  879. hours = date[D.hcGetHours](),
  880. day = date[D.hcGetDay](),
  881. dayOfMonth = date[D.hcGetDate](),
  882. month = date[D.hcGetMonth](),
  883. fullYear = date[D.hcGetFullYear](),
  884. lang = H.defaultOptions.lang,
  885. langWeekdays = lang.weekdays,
  886. shortWeekdays = lang.shortWeekdays,
  887. pad = H.pad,
  888. // List all format keys. Custom formats can be added from the outside.
  889. replacements = H.extend({
  890. //-- Day
  891. // Short weekday, like 'Mon'
  892. 'a': shortWeekdays ?
  893. shortWeekdays[day] : langWeekdays[day].substr(0, 3),
  894. // Long weekday, like 'Monday'
  895. 'A': langWeekdays[day],
  896. // Two digit day of the month, 01 to 31
  897. 'd': pad(dayOfMonth),
  898. // Day of the month, 1 through 31
  899. 'e': pad(dayOfMonth, 2, ' '),
  900. 'w': day,
  901. // Week (none implemented)
  902. //'W': weekNumber(),
  903. //-- Month
  904. // Short month, like 'Jan'
  905. 'b': lang.shortMonths[month],
  906. // Long month, like 'January'
  907. 'B': lang.months[month],
  908. // Two digit month number, 01 through 12
  909. 'm': pad(month + 1),
  910. //-- Year
  911. // Two digits year, like 09 for 2009
  912. 'y': fullYear.toString().substr(2, 2),
  913. // Four digits year, like 2009
  914. 'Y': fullYear,
  915. //-- Time
  916. // Two digits hours in 24h format, 00 through 23
  917. 'H': pad(hours),
  918. // Hours in 24h format, 0 through 23
  919. 'k': hours,
  920. // Two digits hours in 12h format, 00 through 11
  921. 'I': pad((hours % 12) || 12),
  922. // Hours in 12h format, 1 through 12
  923. 'l': (hours % 12) || 12,
  924. // Two digits minutes, 00 through 59
  925. 'M': pad(date[D.hcGetMinutes]()),
  926. // Upper case AM or PM
  927. 'p': hours < 12 ? 'AM' : 'PM',
  928. // Lower case AM or PM
  929. 'P': hours < 12 ? 'am' : 'pm',
  930. // Two digits seconds, 00 through 59
  931. 'S': pad(date.getSeconds()),
  932. // Milliseconds (naming from Ruby)
  933. 'L': pad(Math.round(timestamp % 1000), 3)
  934. },
  935. /**
  936. * A hook for defining additional date format specifiers. New
  937. * specifiers are defined as key-value pairs by using the specifier
  938. * as key, and a function which takes the timestamp as value. This
  939. * function returns the formatted portion of the date.
  940. *
  941. * @type {Object}
  942. * @name dateFormats
  943. * @memberOf Highcharts
  944. * @sample highcharts/global/dateformats/ Adding support for week
  945. * number
  946. */
  947. H.dateFormats
  948. );
  949. // Do the replaces
  950. H.objectEach(replacements, function(val, key) {
  951. // Regex would do it in one line, but this is faster
  952. while (format.indexOf('%' + key) !== -1) {
  953. format = format.replace(
  954. '%' + key,
  955. typeof val === 'function' ? val(timestamp) : val
  956. );
  957. }
  958. });
  959. // Optionally capitalize the string and return
  960. return capitalize ?
  961. format.substr(0, 1).toUpperCase() + format.substr(1) :
  962. format;
  963. };
  964. /**
  965. * Format a single variable. Similar to sprintf, without the % prefix.
  966. *
  967. * @example
  968. * formatSingle('.2f', 5); // => '5.00'.
  969. *
  970. * @function #formatSingle
  971. * @memberOf Highcharts
  972. * @param {String} format The format string.
  973. * @param {*} val The value.
  974. * @returns {String} The formatted representation of the value.
  975. */
  976. H.formatSingle = function(format, val) {
  977. var floatRegex = /f$/,
  978. decRegex = /\.([0-9])/,
  979. lang = H.defaultOptions.lang,
  980. decimals;
  981. if (floatRegex.test(format)) { // float
  982. decimals = format.match(decRegex);
  983. decimals = decimals ? decimals[1] : -1;
  984. if (val !== null) {
  985. val = H.numberFormat(
  986. val,
  987. decimals,
  988. lang.decimalPoint,
  989. format.indexOf(',') > -1 ? lang.thousandsSep : ''
  990. );
  991. }
  992. } else {
  993. val = H.dateFormat(format, val);
  994. }
  995. return val;
  996. };
  997. /**
  998. * Format a string according to a subset of the rules of Python's String.format
  999. * method.
  1000. *
  1001. * @function #format
  1002. * @memberOf Highcharts
  1003. * @param {String} str The string to format.
  1004. * @param {Object} ctx The context, a collection of key-value pairs where each
  1005. * key is replaced by its value.
  1006. * @returns {String} The formatted string.
  1007. *
  1008. * @example
  1009. * var s = Highcharts.format(
  1010. * 'The {color} fox was {len:.2f} feet long',
  1011. * { color: 'red', len: Math.PI }
  1012. * );
  1013. * // => The red fox was 3.14 feet long
  1014. */
  1015. H.format = function(str, ctx) {
  1016. var splitter = '{',
  1017. isInside = false,
  1018. segment,
  1019. valueAndFormat,
  1020. path,
  1021. i,
  1022. len,
  1023. ret = [],
  1024. val,
  1025. index;
  1026. while (str) {
  1027. index = str.indexOf(splitter);
  1028. if (index === -1) {
  1029. break;
  1030. }
  1031. segment = str.slice(0, index);
  1032. if (isInside) { // we're on the closing bracket looking back
  1033. valueAndFormat = segment.split(':');
  1034. path = valueAndFormat.shift().split('.'); // get first and leave
  1035. len = path.length;
  1036. val = ctx;
  1037. // Assign deeper paths
  1038. for (i = 0; i < len; i++) {
  1039. val = val[path[i]];
  1040. }
  1041. // Format the replacement
  1042. if (valueAndFormat.length) {
  1043. val = H.formatSingle(valueAndFormat.join(':'), val);
  1044. }
  1045. // Push the result and advance the cursor
  1046. ret.push(val);
  1047. } else {
  1048. ret.push(segment);
  1049. }
  1050. str = str.slice(index + 1); // the rest
  1051. isInside = !isInside; // toggle
  1052. splitter = isInside ? '}' : '{'; // now look for next matching bracket
  1053. }
  1054. ret.push(str);
  1055. return ret.join('');
  1056. };
  1057. /**
  1058. * Get the magnitude of a number.
  1059. *
  1060. * @function #getMagnitude
  1061. * @memberOf Highcharts
  1062. * @param {Number} number The number.
  1063. * @returns {Number} The magnitude, where 1-9 are magnitude 1, 10-99 magnitude 2
  1064. * etc.
  1065. */
  1066. H.getMagnitude = function(num) {
  1067. return Math.pow(10, Math.floor(Math.log(num) / Math.LN10));
  1068. };
  1069. /**
  1070. * Take an interval and normalize it to multiples of round numbers.
  1071. *
  1072. * @todo Move this function to the Axis prototype. It is here only for
  1073. * historical reasons.
  1074. * @function #normalizeTickInterval
  1075. * @memberOf Highcharts
  1076. * @param {Number} interval - The raw, un-rounded interval.
  1077. * @param {Array} [multiples] - Allowed multiples.
  1078. * @param {Number} [magnitude] - The magnitude of the number.
  1079. * @param {Boolean} [allowDecimals] - Whether to allow decimals.
  1080. * @param {Boolean} [hasTickAmount] - If it has tickAmount, avoid landing
  1081. * on tick intervals lower than original.
  1082. * @returns {Number} The normalized interval.
  1083. */
  1084. H.normalizeTickInterval = function(interval, multiples, magnitude,
  1085. allowDecimals, hasTickAmount) {
  1086. var normalized,
  1087. i,
  1088. retInterval = interval;
  1089. // round to a tenfold of 1, 2, 2.5 or 5
  1090. magnitude = H.pick(magnitude, 1);
  1091. normalized = interval / magnitude;
  1092. // multiples for a linear scale
  1093. if (!multiples) {
  1094. multiples = hasTickAmount ?
  1095. // Finer grained ticks when the tick amount is hard set, including
  1096. // when alignTicks is true on multiple axes (#4580).
  1097. [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10] :
  1098. // Else, let ticks fall on rounder numbers
  1099. [1, 2, 2.5, 5, 10];
  1100. // the allowDecimals option
  1101. if (allowDecimals === false) {
  1102. if (magnitude === 1) {
  1103. multiples = H.grep(multiples, function(num) {
  1104. return num % 1 === 0;
  1105. });
  1106. } else if (magnitude <= 0.1) {
  1107. multiples = [1 / magnitude];
  1108. }
  1109. }
  1110. }
  1111. // normalize the interval to the nearest multiple
  1112. for (i = 0; i < multiples.length; i++) {
  1113. retInterval = multiples[i];
  1114. // only allow tick amounts smaller than natural
  1115. if ((hasTickAmount && retInterval * magnitude >= interval) ||
  1116. (!hasTickAmount && (normalized <= (multiples[i] +
  1117. (multiples[i + 1] || multiples[i])) / 2))) {
  1118. break;
  1119. }
  1120. }
  1121. // Multiply back to the correct magnitude. Correct floats to appropriate
  1122. // precision (#6085).
  1123. retInterval = H.correctFloat(
  1124. retInterval * magnitude, -Math.round(Math.log(0.001) / Math.LN10)
  1125. );
  1126. return retInterval;
  1127. };
  1128. /**
  1129. * Sort an object array and keep the order of equal items. The ECMAScript
  1130. * standard does not specify the behaviour when items are equal.
  1131. *
  1132. * @function #stableSort
  1133. * @memberOf Highcharts
  1134. * @param {Array} arr - The array to sort.
  1135. * @param {Function} sortFunction - The function to sort it with, like with
  1136. * regular Array.prototype.sort.
  1137. * @returns {void}
  1138. */
  1139. H.stableSort = function(arr, sortFunction) {
  1140. var length = arr.length,
  1141. sortValue,
  1142. i;
  1143. // Add index to each item
  1144. for (i = 0; i < length; i++) {
  1145. arr[i].safeI = i; // stable sort index
  1146. }
  1147. arr.sort(function(a, b) {
  1148. sortValue = sortFunction(a, b);
  1149. return sortValue === 0 ? a.safeI - b.safeI : sortValue;
  1150. });
  1151. // Remove index from items
  1152. for (i = 0; i < length; i++) {
  1153. delete arr[i].safeI; // stable sort index
  1154. }
  1155. };
  1156. /**
  1157. * Non-recursive method to find the lowest member of an array. `Math.min` raises
  1158. * a maximum call stack size exceeded error in Chrome when trying to apply more
  1159. * than 150.000 points. This method is slightly slower, but safe.
  1160. *
  1161. * @function #arrayMin
  1162. * @memberOf Highcharts
  1163. * @param {Array} data An array of numbers.
  1164. * @returns {Number} The lowest number.
  1165. */
  1166. H.arrayMin = function(data) {
  1167. var i = data.length,
  1168. min = data[0];
  1169. while (i--) {
  1170. if (data[i] < min) {
  1171. min = data[i];
  1172. }
  1173. }
  1174. return min;
  1175. };
  1176. /**
  1177. * Non-recursive method to find the lowest member of an array. `Math.max` raises
  1178. * a maximum call stack size exceeded error in Chrome when trying to apply more
  1179. * than 150.000 points. This method is slightly slower, but safe.
  1180. *
  1181. * @function #arrayMax
  1182. * @memberOf Highcharts
  1183. * @param {Array} data - An array of numbers.
  1184. * @returns {Number} The highest number.
  1185. */
  1186. H.arrayMax = function(data) {
  1187. var i = data.length,
  1188. max = data[0];
  1189. while (i--) {
  1190. if (data[i] > max) {
  1191. max = data[i];
  1192. }
  1193. }
  1194. return max;
  1195. };
  1196. /**
  1197. * Utility method that destroys any SVGElement instances that are properties on
  1198. * the given object. It loops all properties and invokes destroy if there is a
  1199. * destroy method. The property is then delete.
  1200. *
  1201. * @function #destroyObjectProperties
  1202. * @memberOf Highcharts
  1203. * @param {Object} obj - The object to destroy properties on.
  1204. * @param {Object} [except] - Exception, do not destroy this property, only
  1205. * delete it.
  1206. * @returns {void}
  1207. */
  1208. H.destroyObjectProperties = function(obj, except) {
  1209. H.objectEach(obj, function(val, n) {
  1210. // If the object is non-null and destroy is defined
  1211. if (val && val !== except && val.destroy) {
  1212. // Invoke the destroy
  1213. val.destroy();
  1214. }
  1215. // Delete the property from the object.
  1216. delete obj[n];
  1217. });
  1218. };
  1219. /**
  1220. * Discard a HTML element by moving it to the bin and delete.
  1221. *
  1222. * @function #discardElement
  1223. * @memberOf Highcharts
  1224. * @param {HTMLDOMElement} element - The HTML node to discard.
  1225. * @returns {void}
  1226. */
  1227. H.discardElement = function(element) {
  1228. var garbageBin = H.garbageBin;
  1229. // create a garbage bin element, not part of the DOM
  1230. if (!garbageBin) {
  1231. garbageBin = H.createElement('div');
  1232. }
  1233. // move the node and empty bin
  1234. if (element) {
  1235. garbageBin.appendChild(element);
  1236. }
  1237. garbageBin.innerHTML = '';
  1238. };
  1239. /**
  1240. * Fix JS round off float errors.
  1241. *
  1242. * @function #correctFloat
  1243. * @memberOf Highcharts
  1244. * @param {Number} num - A float number to fix.
  1245. * @param {Number} [prec=14] - The precision.
  1246. * @returns {Number} The corrected float number.
  1247. */
  1248. H.correctFloat = function(num, prec) {
  1249. return parseFloat(
  1250. num.toPrecision(prec || 14)
  1251. );
  1252. };
  1253. /**
  1254. * Set the global animation to either a given value, or fall back to the given
  1255. * chart's animation option.
  1256. *
  1257. * @function #setAnimation
  1258. * @memberOf Highcharts
  1259. * @param {Boolean|Animation} animation - The animation object.
  1260. * @param {Object} chart - The chart instance.
  1261. * @returns {void}
  1262. * @todo This function always relates to a chart, and sets a property on the
  1263. * renderer, so it should be moved to the SVGRenderer.
  1264. */
  1265. H.setAnimation = function(animation, chart) {
  1266. chart.renderer.globalAnimation = H.pick(
  1267. animation,
  1268. chart.options.chart.animation,
  1269. true
  1270. );
  1271. };
  1272. /**
  1273. * Get the animation in object form, where a disabled animation is always
  1274. * returned as `{ duration: 0 }`.
  1275. *
  1276. * @function #animObject
  1277. * @memberOf Highcharts
  1278. * @param {Boolean|AnimationOptions} animation - An animation setting. Can be an
  1279. * object with duration, complete and easing properties, or a boolean to
  1280. * enable or disable.
  1281. * @returns {AnimationOptions} An object with at least a duration property.
  1282. */
  1283. H.animObject = function(animation) {
  1284. return H.isObject(animation) ?
  1285. H.merge(animation) : {
  1286. duration: animation ? 500 : 0
  1287. };
  1288. };
  1289. /**
  1290. * The time unit lookup
  1291. */
  1292. H.timeUnits = {
  1293. millisecond: 1,
  1294. second: 1000,
  1295. minute: 60000,
  1296. hour: 3600000,
  1297. day: 24 * 3600000,
  1298. week: 7 * 24 * 3600000,
  1299. month: 28 * 24 * 3600000,
  1300. year: 364 * 24 * 3600000
  1301. };
  1302. /**
  1303. * Format a number and return a string based on input settings.
  1304. *
  1305. * @function #numberFormat
  1306. * @memberOf Highcharts
  1307. * @param {Number} number - The input number to format.
  1308. * @param {Number} decimals - The amount of decimals. A value of -1 preserves
  1309. * the amount in the input number.
  1310. * @param {String} [decimalPoint] - The decimal point, defaults to the one given
  1311. * in the lang options, or a dot.
  1312. * @param {String} [thousandsSep] - The thousands separator, defaults to the one
  1313. * given in the lang options, or a space character.
  1314. * @returns {String} The formatted number.
  1315. *
  1316. * @sample members/highcharts-numberformat/ Custom number format
  1317. */
  1318. H.numberFormat = function(number, decimals, decimalPoint, thousandsSep) {
  1319. number = +number || 0;
  1320. decimals = +decimals;
  1321. var lang = H.defaultOptions.lang,
  1322. origDec = (number.toString().split('.')[1] || '').length,
  1323. strinteger,
  1324. thousands,
  1325. ret,
  1326. roundedNumber;
  1327. if (decimals === -1) {
  1328. // Preserve decimals. Not huge numbers (#3793).
  1329. decimals = Math.min(origDec, 20);
  1330. } else if (!H.isNumber(decimals)) {
  1331. decimals = 2;
  1332. }
  1333. // Add another decimal to avoid rounding errors of float numbers. (#4573)
  1334. // Then use toFixed to handle rounding.
  1335. roundedNumber = (
  1336. Math.abs(number) + Math.pow(10, -Math.max(decimals, origDec) - 1)
  1337. ).toFixed(decimals);
  1338. // A string containing the positive integer component of the number
  1339. strinteger = String(H.pInt(roundedNumber));
  1340. // Leftover after grouping into thousands. Can be 0, 1 or 3.
  1341. thousands = strinteger.length > 3 ? strinteger.length % 3 : 0;
  1342. // Language
  1343. decimalPoint = H.pick(decimalPoint, lang.decimalPoint);
  1344. thousandsSep = H.pick(thousandsSep, lang.thousandsSep);
  1345. // Start building the return
  1346. ret = number < 0 ? '-' : '';
  1347. // Add the leftover after grouping into thousands. For example, in the
  1348. // number 42 000 000, this line adds 42.
  1349. ret += thousands ? strinteger.substr(0, thousands) + thousandsSep : '';
  1350. // Add the remaining thousands groups, joined by the thousands separator
  1351. ret += strinteger
  1352. .substr(thousands)
  1353. .replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep);
  1354. // Add the decimal point and the decimal component
  1355. if (decimals) {
  1356. // Get the decimal component
  1357. ret += decimalPoint + roundedNumber.slice(-decimals);
  1358. }
  1359. return ret;
  1360. };
  1361. /**
  1362. * Easing definition
  1363. * @ignore
  1364. * @param {Number} pos Current position, ranging from 0 to 1.
  1365. */
  1366. Math.easeInOutSine = function(pos) {
  1367. return -0.5 * (Math.cos(Math.PI * pos) - 1);
  1368. };
  1369. /**
  1370. * Get the computed CSS value for given element and property, only for numerical
  1371. * properties. For width and height, the dimension of the inner box (excluding
  1372. * padding) is returned. Used for fitting the chart within the container.
  1373. *
  1374. * @function #getStyle
  1375. * @memberOf Highcharts
  1376. * @param {HTMLDOMElement} el - A HTML element.
  1377. * @param {String} prop - The property name.
  1378. * @param {Boolean} [toInt=true] - Parse to integer.
  1379. * @returns {Number} - The numeric value.
  1380. */
  1381. H.getStyle = function(el, prop, toInt) {
  1382. var style;
  1383. // For width and height, return the actual inner pixel size (#4913)
  1384. if (prop === 'width') {
  1385. return Math.min(el.offsetWidth, el.scrollWidth) -
  1386. H.getStyle(el, 'padding-left') -
  1387. H.getStyle(el, 'padding-right');
  1388. } else if (prop === 'height') {
  1389. return Math.min(el.offsetHeight, el.scrollHeight) -
  1390. H.getStyle(el, 'padding-top') -
  1391. H.getStyle(el, 'padding-bottom');
  1392. }
  1393. // Otherwise, get the computed style
  1394. style = win.getComputedStyle(el, undefined);
  1395. if (style) {
  1396. style = style.getPropertyValue(prop);
  1397. if (H.pick(toInt, true)) {
  1398. style = H.pInt(style);
  1399. }
  1400. }
  1401. return style;
  1402. };
  1403. /**
  1404. * Search for an item in an array.
  1405. *
  1406. * @function #inArray
  1407. * @memberOf Highcharts
  1408. * @param {*} item - The item to search for.
  1409. * @param {arr} arr - The array or node collection to search in.
  1410. * @returns {Number} - The index within the array, or -1 if not found.
  1411. */
  1412. H.inArray = function(item, arr) {
  1413. return arr.indexOf ? arr.indexOf(item) : [].indexOf.call(arr, item);
  1414. };
  1415. /**
  1416. * Filter an array by a callback.
  1417. *
  1418. * @function #grep
  1419. * @memberOf Highcharts
  1420. * @param {Array} arr - The array to filter.
  1421. * @param {Function} callback - The callback function. The function receives the
  1422. * item as the first argument. Return `true` if the item is to be
  1423. * preserved.
  1424. * @returns {Array} - A new, filtered array.
  1425. */
  1426. H.grep = function(arr, callback) {
  1427. return [].filter.call(arr, callback);
  1428. };
  1429. /**
  1430. * Return the value of the first element in the array that satisfies the
  1431. * provided testing function.
  1432. *
  1433. * @function #find
  1434. * @memberOf Highcharts
  1435. * @param {Array} arr - The array to test.
  1436. * @param {Function} callback - The callback function. The function receives the
  1437. * item as the first argument. Return `true` if this item satisfies the
  1438. * condition.
  1439. * @returns {Mixed} - The value of the element.
  1440. */
  1441. H.find = function(arr, callback) {
  1442. return [].find.call(arr, callback);
  1443. };
  1444. /**
  1445. * Map an array by a callback.
  1446. *
  1447. * @function #map
  1448. * @memberOf Highcharts
  1449. * @param {Array} arr - The array to map.
  1450. * @param {Function} fn - The callback function. Return the new value for the
  1451. * new array.
  1452. * @returns {Array} - A new array item with modified items.
  1453. */
  1454. H.map = function(arr, fn) {
  1455. var results = [],
  1456. i = 0,
  1457. len = arr.length;
  1458. for (; i < len; i++) {
  1459. results[i] = fn.call(arr[i], arr[i], i, arr);
  1460. }
  1461. return results;
  1462. };
  1463. /**
  1464. * Get the element's offset position, corrected for `overflow: auto`.
  1465. *
  1466. * @function #offset
  1467. * @memberOf Highcharts
  1468. * @param {HTMLDOMElement} el - The HTML element.
  1469. * @returns {Object} An object containing `left` and `top` properties for the
  1470. * position in the page.
  1471. */
  1472. H.offset = function(el) {
  1473. var docElem = doc.documentElement,
  1474. box = el.getBoundingClientRect();
  1475. return {
  1476. top: box.top + (win.pageYOffset || docElem.scrollTop) -
  1477. (docElem.clientTop || 0),
  1478. left: box.left + (win.pageXOffset || docElem.scrollLeft) -
  1479. (docElem.clientLeft || 0)
  1480. };
  1481. };
  1482. /**
  1483. * Stop running animation.
  1484. *
  1485. * @todo A possible extension to this would be to stop a single property, when
  1486. * we want to continue animating others. Then assign the prop to the timer
  1487. * in the Fx.run method, and check for the prop here. This would be an
  1488. * improvement in all cases where we stop the animation from .attr. Instead of
  1489. * stopping everything, we can just stop the actual attributes we're setting.
  1490. *
  1491. * @function #stop
  1492. * @memberOf Highcharts
  1493. * @param {SVGElement} el - The SVGElement to stop animation on.
  1494. * @param {string} [prop] - The property to stop animating. If given, the stop
  1495. * method will stop a single property from animating, while others continue.
  1496. * @returns {void}
  1497. */
  1498. H.stop = function(el, prop) {
  1499. var i = timers.length;
  1500. // Remove timers related to this element (#4519)
  1501. while (i--) {
  1502. if (timers[i].elem === el && (!prop || prop === timers[i].prop)) {
  1503. timers[i].stopped = true; // #4667
  1504. }
  1505. }
  1506. };
  1507. /**
  1508. * Iterate over an array.
  1509. *
  1510. * @function #each
  1511. * @memberOf Highcharts
  1512. * @param {Array} arr - The array to iterate over.
  1513. * @param {Function} fn - The iterator callback. It passes three arguments:
  1514. * * item - The array item.
  1515. * * index - The item's index in the array.
  1516. * * arr - The array that each is being applied to.
  1517. * @param {Object} [ctx] The context.
  1518. */
  1519. H.each = function(arr, fn, ctx) { // modern browsers
  1520. return Array.prototype.forEach.call(arr, fn, ctx);
  1521. };
  1522. /**
  1523. * Iterate over object key pairs in an object.
  1524. *
  1525. * @function #objectEach
  1526. * @memberOf Highcharts
  1527. * @param {Object} obj - The object to iterate over.
  1528. * @param {Function} fn - The iterator callback. It passes three arguments:
  1529. * * value - The property value.
  1530. * * key - The property key.
  1531. * * obj - The object that objectEach is being applied to.
  1532. * @param {Object} ctx The context
  1533. */
  1534. H.objectEach = function(obj, fn, ctx) {
  1535. for (var key in obj) {
  1536. if (obj.hasOwnProperty(key)) {
  1537. fn.call(ctx, obj[key], key, obj);
  1538. }
  1539. }
  1540. };
  1541. /**
  1542. * Add an event listener.
  1543. *
  1544. * @function #addEvent
  1545. * @memberOf Highcharts
  1546. * @param {Object} el - The element or object to add a listener to. It can be a
  1547. * {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
  1548. * @param {String} type - The event type.
  1549. * @param {Function} fn - The function callback to execute when the event is
  1550. * fired.
  1551. * @returns {Function} A callback function to remove the added event.
  1552. */
  1553. H.addEvent = function(el, type, fn) {
  1554. var events = el.hcEvents = el.hcEvents || {};
  1555. function wrappedFn(e) {
  1556. e.target = e.srcElement || win; // #2820
  1557. fn.call(el, e);
  1558. }
  1559. // Handle DOM events in modern browsers
  1560. if (el.addEventListener) {
  1561. el.addEventListener(type, fn, false);
  1562. // Handle old IE implementation
  1563. } else if (el.attachEvent) {
  1564. if (!el.hcEventsIE) {
  1565. el.hcEventsIE = {};
  1566. }
  1567. // Link wrapped fn with original fn, so we can get this in removeEvent
  1568. el.hcEventsIE[fn.toString()] = wrappedFn;
  1569. el.attachEvent('on' + type, wrappedFn);
  1570. }
  1571. if (!events[type]) {
  1572. events[type] = [];
  1573. }
  1574. events[type].push(fn);
  1575. // Return a function that can be called to remove this event.
  1576. return function() {
  1577. H.removeEvent(el, type, fn);
  1578. };
  1579. };
  1580. /**
  1581. * Remove an event that was added with {@link Highcharts#addEvent}.
  1582. *
  1583. * @function #removeEvent
  1584. * @memberOf Highcharts
  1585. * @param {Object} el - The element to remove events on.
  1586. * @param {String} [type] - The type of events to remove. If undefined, all
  1587. * events are removed from the element.
  1588. * @param {Function} [fn] - The specific callback to remove. If undefined, all
  1589. * events that match the element and optionally the type are removed.
  1590. * @returns {void}
  1591. */
  1592. H.removeEvent = function(el, type, fn) {
  1593. var events,
  1594. hcEvents = el.hcEvents,
  1595. index;
  1596. function removeOneEvent(type, fn) {
  1597. if (el.removeEventListener) {
  1598. el.removeEventListener(type, fn, false);
  1599. } else if (el.attachEvent) {
  1600. fn = el.hcEventsIE[fn.toString()];
  1601. el.detachEvent('on' + type, fn);
  1602. }
  1603. }
  1604. function removeAllEvents() {
  1605. var types,
  1606. len;
  1607. if (!el.nodeName) {
  1608. return; // break on non-DOM events
  1609. }
  1610. if (type) {
  1611. types = {};
  1612. types[type] = true;
  1613. } else {
  1614. types = hcEvents;
  1615. }
  1616. H.objectEach(types, function(val, n) {
  1617. if (hcEvents[n]) {
  1618. len = hcEvents[n].length;
  1619. while (len--) {
  1620. removeOneEvent(n, hcEvents[n][len]);
  1621. }
  1622. }
  1623. });
  1624. }
  1625. if (hcEvents) {
  1626. if (type) {
  1627. events = hcEvents[type] || [];
  1628. if (fn) {
  1629. index = H.inArray(fn, events);
  1630. if (index > -1) {
  1631. events.splice(index, 1);
  1632. hcEvents[type] = events;
  1633. }
  1634. removeOneEvent(type, fn);
  1635. } else {
  1636. removeAllEvents();
  1637. hcEvents[type] = [];
  1638. }
  1639. } else {
  1640. removeAllEvents();
  1641. el.hcEvents = {};
  1642. }
  1643. }
  1644. };
  1645. /**
  1646. * Fire an event that was registered with {@link Highcharts#addEvent}.
  1647. *
  1648. * @function #fireEvent
  1649. * @memberOf Highcharts
  1650. * @param {Object} el - The object to fire the event on. It can be a
  1651. * {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
  1652. * @param {String} type - The type of event.
  1653. * @param {Object} [eventArguments] - Custom event arguments that are passed on
  1654. * as an argument to the event handler.
  1655. * @param {Function} [defaultFunction] - The default function to execute if the
  1656. * other listeners haven't returned false.
  1657. * @returns {void}
  1658. */
  1659. H.fireEvent = function(el, type, eventArguments, defaultFunction) {
  1660. var e,
  1661. hcEvents = el.hcEvents,
  1662. events,
  1663. len,
  1664. i,
  1665. fn;
  1666. eventArguments = eventArguments || {};
  1667. if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {
  1668. e = doc.createEvent('Events');
  1669. e.initEvent(type, true, true);
  1670. //e.target = el;
  1671. H.extend(e, eventArguments);
  1672. if (el.dispatchEvent) {
  1673. el.dispatchEvent(e);
  1674. } else {
  1675. el.fireEvent(type, e);
  1676. }
  1677. } else if (hcEvents) {
  1678. events = hcEvents[type] || [];
  1679. len = events.length;
  1680. if (!eventArguments.target) { // We're running a custom event
  1681. H.extend(eventArguments, {
  1682. // Attach a simple preventDefault function to skip default
  1683. // handler if called. The built-in defaultPrevented property is
  1684. // not overwritable (#5112)
  1685. preventDefault: function() {
  1686. eventArguments.defaultPrevented = true;
  1687. },
  1688. // Setting target to native events fails with clicking the
  1689. // zoom-out button in Chrome.
  1690. target: el,
  1691. // If the type is not set, we're running a custom event (#2297).
  1692. // If it is set, we're running a browser event, and setting it
  1693. // will cause en error in IE8 (#2465).
  1694. type: type
  1695. });
  1696. }
  1697. for (i = 0; i < len; i++) {
  1698. fn = events[i];
  1699. // If the event handler return false, prevent the default handler
  1700. // from executing
  1701. if (fn && fn.call(el, eventArguments) === false) {
  1702. eventArguments.preventDefault();
  1703. }
  1704. }
  1705. }
  1706. // Run the default if not prevented
  1707. if (defaultFunction && !eventArguments.defaultPrevented) {
  1708. defaultFunction(eventArguments);
  1709. }
  1710. };
  1711. /**
  1712. * An animation configuration. Animation configurations can also be defined as
  1713. * booleans, where `false` turns off animation and `true` defaults to a duration
  1714. * of 500ms.
  1715. * @typedef {Object} AnimationOptions
  1716. * @property {Number} duration - The animation duration in milliseconds.
  1717. * @property {String} [easing] - The name of an easing function as defined on
  1718. * the `Math` object.
  1719. * @property {Function} [complete] - A callback function to exectute when the
  1720. * animation finishes.
  1721. * @property {Function} [step] - A callback function to execute on each step of
  1722. * each attribute or CSS property that's being animated. The first argument
  1723. * contains information about the animation and progress.
  1724. */
  1725. /**
  1726. * The global animate method, which uses Fx to create individual animators.
  1727. *
  1728. * @function #animate
  1729. * @memberOf Highcharts
  1730. * @param {HTMLDOMElement|SVGElement} el - The element to animate.
  1731. * @param {Object} params - An object containing key-value pairs of the
  1732. * properties to animate. Supports numeric as pixel-based CSS properties
  1733. * for HTML objects and attributes for SVGElements.
  1734. * @param {AnimationOptions} [opt] - Animation options.
  1735. */
  1736. H.animate = function(el, params, opt) {
  1737. var start,
  1738. unit = '',
  1739. end,
  1740. fx,
  1741. args;
  1742. if (!H.isObject(opt)) { // Number or undefined/null
  1743. args = arguments;
  1744. opt = {
  1745. duration: args[2],
  1746. easing: args[3],
  1747. complete: args[4]
  1748. };
  1749. }
  1750. if (!H.isNumber(opt.duration)) {
  1751. opt.duration = 400;
  1752. }
  1753. opt.easing = typeof opt.easing === 'function' ?
  1754. opt.easing :
  1755. (Math[opt.easing] || Math.easeInOutSine);
  1756. opt.curAnim = H.merge(params);
  1757. H.objectEach(params, function(val, prop) {
  1758. // Stop current running animation of this property
  1759. H.stop(el, prop);
  1760. fx = new H.Fx(el, opt, prop);
  1761. end = null;
  1762. if (prop === 'd') {
  1763. fx.paths = fx.initPath(
  1764. el,
  1765. el.d,
  1766. params.d
  1767. );
  1768. fx.toD = params.d;
  1769. start = 0;
  1770. end = 1;
  1771. } else if (el.attr) {
  1772. start = el.attr(prop);
  1773. } else {
  1774. start = parseFloat(H.getStyle(el, prop)) || 0;
  1775. if (prop !== 'opacity') {
  1776. unit = 'px';
  1777. }
  1778. }
  1779. if (!end) {
  1780. end = val;
  1781. }
  1782. if (end && end.match && end.match('px')) {
  1783. end = end.replace(/px/g, ''); // #4351
  1784. }
  1785. fx.run(start, end, unit);
  1786. });
  1787. };
  1788. /**
  1789. * Factory to create new series prototypes.
  1790. *
  1791. * @function #seriesType
  1792. * @memberOf Highcharts
  1793. *
  1794. * @param {String} type - The series type name.
  1795. * @param {String} parent - The parent series type name. Use `line` to inherit
  1796. * from the basic {@link Series} object.
  1797. * @param {Object} options - The additional default options that is merged with
  1798. * the parent's options.
  1799. * @param {Object} props - The properties (functions and primitives) to set on
  1800. * the new prototype.
  1801. * @param {Object} [pointProps] - Members for a series-specific extension of the
  1802. * {@link Point} prototype if needed.
  1803. * @returns {*} - The newly created prototype as extended from {@link Series}
  1804. * or its derivatives.
  1805. */
  1806. // docs: add to API + extending Highcharts
  1807. H.seriesType = function(type, parent, options, props, pointProps) {
  1808. var defaultOptions = H.getOptions(),
  1809. seriesTypes = H.seriesTypes;
  1810. if (seriesTypes[type]) {
  1811. return H.error(27); // Series type already defined
  1812. }
  1813. // Merge the options
  1814. defaultOptions.plotOptions[type] = H.merge(
  1815. defaultOptions.plotOptions[parent],
  1816. options
  1817. );
  1818. // Create the class
  1819. seriesTypes[type] = H.extendClass(seriesTypes[parent] ||
  1820. function() {}, props);
  1821. seriesTypes[type].prototype.type = type;
  1822. // Create the point class if needed
  1823. if (pointProps) {
  1824. seriesTypes[type].prototype.pointClass =
  1825. H.extendClass(H.Point, pointProps);
  1826. }
  1827. return seriesTypes[type];
  1828. };
  1829. /**
  1830. * Get a unique key for using in internal element id's and pointers. The key
  1831. * is composed of a random hash specific to this Highcharts instance, and a
  1832. * counter.
  1833. * @function #uniqueKey
  1834. * @memberOf Highcharts
  1835. * @return {string} The key.
  1836. * @example
  1837. * var id = H.uniqueKey(); // => 'highcharts-x45f6hp-0'
  1838. */
  1839. H.uniqueKey = (function() {
  1840. var uniqueKeyHash = Math.random().toString(36).substring(2, 9),
  1841. idCounter = 0;
  1842. return function() {
  1843. return 'highcharts-' + uniqueKeyHash + '-' + idCounter++;
  1844. };
  1845. }());
  1846. /**
  1847. * Register Highcharts as a plugin in jQuery
  1848. */
  1849. if (win.jQuery) {
  1850. win.jQuery.fn.highcharts = function() {
  1851. var args = [].slice.call(arguments);
  1852. if (this[0]) { // this[0] is the renderTo div
  1853. // Create the chart
  1854. if (args[0]) {
  1855. new H[ // eslint-disable-line no-new
  1856. // Constructor defaults to Chart
  1857. H.isString(args[0]) ? args.shift() : 'Chart'
  1858. ](this[0], args[0], args[1]);
  1859. return this;
  1860. }
  1861. // When called without parameters or with the return argument,
  1862. // return an existing chart
  1863. return charts[H.attr(this[0], 'data-highcharts-chart')];
  1864. }
  1865. };
  1866. }
  1867. /**
  1868. * Compatibility section to add support for legacy IE. This can be removed if
  1869. * old IE support is not needed.
  1870. */
  1871. if (doc && !doc.defaultView) {
  1872. H.getStyle = function(el, prop) {
  1873. var val,
  1874. alias = {
  1875. width: 'clientWidth',
  1876. height: 'clientHeight'
  1877. }[prop];
  1878. if (el.style[prop]) {
  1879. return H.pInt(el.style[prop]);
  1880. }
  1881. if (prop === 'opacity') {
  1882. prop = 'filter';
  1883. }
  1884. // Getting the rendered width and height
  1885. if (alias) {
  1886. el.style.zoom = 1;
  1887. return Math.max(el[alias] - 2 * H.getStyle(el, 'padding'), 0);
  1888. }
  1889. val = el.currentStyle[prop.replace(/\-(\w)/g, function(a, b) {
  1890. return b.toUpperCase();
  1891. })];
  1892. if (prop === 'filter') {
  1893. val = val.replace(
  1894. /alpha\(opacity=([0-9]+)\)/,
  1895. function(a, b) {
  1896. return b / 100;
  1897. }
  1898. );
  1899. }
  1900. return val === '' ? 1 : H.pInt(val);
  1901. };
  1902. }
  1903. if (!Array.prototype.forEach) {
  1904. H.each = function(arr, fn, ctx) { // legacy
  1905. var i = 0,
  1906. len = arr.length;
  1907. for (; i < len; i++) {
  1908. if (fn.call(ctx, arr[i], i, arr) === false) {
  1909. return i;
  1910. }
  1911. }
  1912. };
  1913. }
  1914. if (!Array.prototype.indexOf) {
  1915. H.inArray = function(item, arr) {
  1916. var len,
  1917. i = 0;
  1918. if (arr) {
  1919. len = arr.length;
  1920. for (; i < len; i++) {
  1921. if (arr[i] === item) {
  1922. return i;
  1923. }
  1924. }
  1925. }
  1926. return -1;
  1927. };
  1928. }
  1929. if (!Array.prototype.filter) {
  1930. H.grep = function(elements, fn) {
  1931. var ret = [],
  1932. i = 0,
  1933. length = elements.length;
  1934. for (; i < length; i++) {
  1935. if (fn(elements[i], i)) {
  1936. ret.push(elements[i]);
  1937. }
  1938. }
  1939. return ret;
  1940. };
  1941. }
  1942. if (!Array.prototype.find) {
  1943. H.find = function(arr, fn) {
  1944. var i,
  1945. length = arr.length;
  1946. for (i = 0; i < length; i++) {
  1947. if (fn(arr[i], i)) {
  1948. return arr[i];
  1949. }
  1950. }
  1951. };
  1952. }
  1953. //--- End compatibility section ---
  1954. }(Highcharts));
  1955. (function(H) {
  1956. /**
  1957. * (c) 2010-2017 Torstein Honsi
  1958. *
  1959. * License: www.highcharts.com/license
  1960. */
  1961. var each = H.each,
  1962. isNumber = H.isNumber,
  1963. map = H.map,
  1964. merge = H.merge,
  1965. pInt = H.pInt;
  1966. /**
  1967. * @typedef {string} ColorString
  1968. * A valid color to be parsed and handled by Highcharts. Highcharts internally
  1969. * supports hex colors like `#ffffff`, rgb colors like `rgb(255,255,255)` and
  1970. * rgba colors like `rgba(255,255,255,1)`. Other colors may be supported by the
  1971. * browsers and displayed correctly, but Highcharts is not able to process them
  1972. * and apply concepts like opacity and brightening.
  1973. */
  1974. /**
  1975. * Handle color operations. The object methods are chainable.
  1976. * @param {String} input The input color in either rbga or hex format
  1977. */
  1978. H.Color = function(input) {
  1979. // Backwards compatibility, allow instanciation without new
  1980. if (!(this instanceof H.Color)) {
  1981. return new H.Color(input);
  1982. }
  1983. // Initialize
  1984. this.init(input);
  1985. };
  1986. H.Color.prototype = {
  1987. // Collection of parsers. This can be extended from the outside by pushing parsers
  1988. // to Highcharts.Color.prototype.parsers.
  1989. parsers: [{
  1990. // RGBA color
  1991. regex: /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,
  1992. parse: function(result) {
  1993. return [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
  1994. }
  1995. }, {
  1996. // RGB color
  1997. regex: /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,
  1998. parse: function(result) {
  1999. return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
  2000. }
  2001. }],
  2002. // Collection of named colors. Can be extended from the outside by adding
  2003. // colors to Highcharts.Color.prototype.names.
  2004. names: {
  2005. none: 'rgba(255,255,255,0)',
  2006. white: '#ffffff',
  2007. black: '#000000'
  2008. },
  2009. /**
  2010. * Parse the input color to rgba array
  2011. * @param {String} input
  2012. */
  2013. init: function(input) {
  2014. var result,
  2015. rgba,
  2016. i,
  2017. parser,
  2018. len;
  2019. this.input = input = this.names[
  2020. input && input.toLowerCase ?
  2021. input.toLowerCase() :
  2022. ''
  2023. ] || input;
  2024. // Gradients
  2025. if (input && input.stops) {
  2026. this.stops = map(input.stops, function(stop) {
  2027. return new H.Color(stop[1]);
  2028. });
  2029. // Solid colors
  2030. } else {
  2031. // Check if it's possible to do bitmasking instead of regex
  2032. if (input && input[0] === '#') {
  2033. len = input.length;
  2034. input = parseInt(input.substr(1), 16);
  2035. // Handle long-form, e.g. #AABBCC
  2036. if (len === 7) {
  2037. rgba = [
  2038. (input & 0xFF0000) >> 16,
  2039. (input & 0xFF00) >> 8,
  2040. (input & 0xFF),
  2041. 1
  2042. ];
  2043. // Handle short-form, e.g. #ABC
  2044. // In short form, the value is assumed to be the same
  2045. // for both nibbles for each component. e.g. #ABC = #AABBCC
  2046. } else if (len === 4) {
  2047. rgba = [
  2048. ((input & 0xF00) >> 4) | (input & 0xF00) >> 8,
  2049. ((input & 0xF0) >> 4) | (input & 0xF0),
  2050. ((input & 0xF) << 4) | (input & 0xF),
  2051. 1
  2052. ];
  2053. }
  2054. }
  2055. // Otherwise, check regex parsers
  2056. if (!rgba) {
  2057. i = this.parsers.length;
  2058. while (i-- && !rgba) {
  2059. parser = this.parsers[i];
  2060. result = parser.regex.exec(input);
  2061. if (result) {
  2062. rgba = parser.parse(result);
  2063. }
  2064. }
  2065. }
  2066. }
  2067. this.rgba = rgba || [];
  2068. },
  2069. /**
  2070. * Return the color a specified format
  2071. * @param {String} format
  2072. */
  2073. get: function(format) {
  2074. var input = this.input,
  2075. rgba = this.rgba,
  2076. ret;
  2077. if (this.stops) {
  2078. ret = merge(input);
  2079. ret.stops = [].concat(ret.stops);
  2080. each(this.stops, function(stop, i) {
  2081. ret.stops[i] = [ret.stops[i][0], stop.get(format)];
  2082. });
  2083. // it's NaN if gradient colors on a column chart
  2084. } else if (rgba && isNumber(rgba[0])) {
  2085. if (format === 'rgb' || (!format && rgba[3] === 1)) {
  2086. ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
  2087. } else if (format === 'a') {
  2088. ret = rgba[3];
  2089. } else {
  2090. ret = 'rgba(' + rgba.join(',') + ')';
  2091. }
  2092. } else {
  2093. ret = input;
  2094. }
  2095. return ret;
  2096. },
  2097. /**
  2098. * Brighten the color
  2099. * @param {Number} alpha
  2100. */
  2101. brighten: function(alpha) {
  2102. var i,
  2103. rgba = this.rgba;
  2104. if (this.stops) {
  2105. each(this.stops, function(stop) {
  2106. stop.brighten(alpha);
  2107. });
  2108. } else if (isNumber(alpha) && alpha !== 0) {
  2109. for (i = 0; i < 3; i++) {
  2110. rgba[i] += pInt(alpha * 255);
  2111. if (rgba[i] < 0) {
  2112. rgba[i] = 0;
  2113. }
  2114. if (rgba[i] > 255) {
  2115. rgba[i] = 255;
  2116. }
  2117. }
  2118. }
  2119. return this;
  2120. },
  2121. /**
  2122. * Set the color's opacity to a given alpha value
  2123. * @param {Number} alpha
  2124. */
  2125. setOpacity: function(alpha) {
  2126. this.rgba[3] = alpha;
  2127. return this;
  2128. },
  2129. /*
  2130. * Return an intermediate color between two colors.
  2131. *
  2132. * @param {Highcharts.Color} to
  2133. * The color object to tween to.
  2134. * @param {Number} pos
  2135. * The intermediate position, where 0 is the from color (current
  2136. * color item), and 1 is the `to` color.
  2137. *
  2138. * @return {String}
  2139. * The intermediate color in rgba notation.
  2140. */
  2141. tweenTo: function(to, pos) {
  2142. // Check for has alpha, because rgba colors perform worse due to lack of
  2143. // support in WebKit.
  2144. var from = this,
  2145. hasAlpha,
  2146. ret;
  2147. // Unsupported color, return to-color (#3920)
  2148. if (!to.rgba.length) {
  2149. ret = to.input || 'none';
  2150. // Interpolate
  2151. } else {
  2152. from = from.rgba;
  2153. to = to.rgba;
  2154. hasAlpha = (to[3] !== 1 || from[3] !== 1);
  2155. ret = (hasAlpha ? 'rgba(' : 'rgb(') +
  2156. Math.round(to[0] + (from[0] - to[0]) * (1 - pos)) + ',' +
  2157. Math.round(to[1] + (from[1] - to[1]) * (1 - pos)) + ',' +
  2158. Math.round(to[2] + (from[2] - to[2]) * (1 - pos)) +
  2159. (hasAlpha ?
  2160. (',' + (to[3] + (from[3] - to[3]) * (1 - pos))) :
  2161. '') + ')';
  2162. }
  2163. return ret;
  2164. }
  2165. };
  2166. H.color = function(input) {
  2167. return new H.Color(input);
  2168. };
  2169. }(Highcharts));
  2170. (function(H) {
  2171. /**
  2172. * (c) 2010-2017 Torstein Honsi
  2173. *
  2174. * License: www.highcharts.com/license
  2175. */
  2176. var SVGElement,
  2177. SVGRenderer,
  2178. addEvent = H.addEvent,
  2179. animate = H.animate,
  2180. attr = H.attr,
  2181. charts = H.charts,
  2182. color = H.color,
  2183. css = H.css,
  2184. createElement = H.createElement,
  2185. defined = H.defined,
  2186. deg2rad = H.deg2rad,
  2187. destroyObjectProperties = H.destroyObjectProperties,
  2188. doc = H.doc,
  2189. each = H.each,
  2190. extend = H.extend,
  2191. erase = H.erase,
  2192. grep = H.grep,
  2193. hasTouch = H.hasTouch,
  2194. inArray = H.inArray,
  2195. isArray = H.isArray,
  2196. isFirefox = H.isFirefox,
  2197. isMS = H.isMS,
  2198. isObject = H.isObject,
  2199. isString = H.isString,
  2200. isWebKit = H.isWebKit,
  2201. merge = H.merge,
  2202. noop = H.noop,
  2203. objectEach = H.objectEach,
  2204. pick = H.pick,
  2205. pInt = H.pInt,
  2206. removeEvent = H.removeEvent,
  2207. splat = H.splat,
  2208. stop = H.stop,
  2209. svg = H.svg,
  2210. SVG_NS = H.SVG_NS,
  2211. symbolSizes = H.symbolSizes,
  2212. win = H.win;
  2213. /**
  2214. * @typedef {Object} SVGDOMElement - An SVG DOM element.
  2215. */
  2216. /**
  2217. * The SVGElement prototype is a JavaScript wrapper for SVG elements used in the
  2218. * rendering layer of Highcharts. Combined with the {@link
  2219. * Highcharts.SVGRenderer} object, these prototypes allow freeform annotation
  2220. * in the charts or even in HTML pages without instanciating a chart. The
  2221. * SVGElement can also wrap HTML labels, when `text` or `label` elements are
  2222. * created with the `useHTML` parameter.
  2223. *
  2224. * The SVGElement instances are created through factory functions on the
  2225. * {@link Highcharts.SVGRenderer} object, like
  2226. * [rect]{@link Highcharts.SVGRenderer#rect}, [path]{@link
  2227. * Highcharts.SVGRenderer#path}, [text]{@link Highcharts.SVGRenderer#text},
  2228. * [label]{@link Highcharts.SVGRenderer#label}, [g]{@link
  2229. * Highcharts.SVGRenderer#g} and more.
  2230. *
  2231. * @class Highcharts.SVGElement
  2232. */
  2233. SVGElement = H.SVGElement = function() {
  2234. return this;
  2235. };
  2236. extend(SVGElement.prototype, /** @lends Highcharts.SVGElement.prototype */ {
  2237. // Default base for animation
  2238. opacity: 1,
  2239. SVG_NS: SVG_NS,
  2240. /**
  2241. * For labels, these CSS properties are applied to the `text` node directly.
  2242. * @type {Array.<string>}
  2243. */
  2244. textProps: ['direction', 'fontSize', 'fontWeight', 'fontFamily',
  2245. 'fontStyle', 'color', 'lineHeight', 'width', 'textAlign',
  2246. 'textDecoration', 'textOverflow', 'textOutline'
  2247. ],
  2248. /**
  2249. * Initialize the SVG renderer. This function only exists to make the
  2250. * initiation process overridable. It should not be called directly.
  2251. *
  2252. * @param {HighchartsSVGRenderer} renderer
  2253. * The SVGRenderer instance to initialize to.
  2254. * @param {String} nodeName
  2255. * The SVG node name.
  2256. * @returns {void}
  2257. */
  2258. init: function(renderer, nodeName) {
  2259. /**
  2260. * The DOM node. Each SVGRenderer instance wraps a main DOM node, but
  2261. * may also represent more nodes.
  2262. * @type {SVGDOMNode|HTMLDOMNode}
  2263. */
  2264. this.element = nodeName === 'span' ?
  2265. createElement(nodeName) :
  2266. doc.createElementNS(this.SVG_NS, nodeName);
  2267. /**
  2268. * The renderer that the SVGElement belongs to.
  2269. * @type {Highcharts.SVGRenderer}
  2270. */
  2271. this.renderer = renderer;
  2272. },
  2273. /**
  2274. * Animate to given attributes or CSS properties.
  2275. *
  2276. * @param {SVGAttributes} params SVG attributes or CSS to animate.
  2277. * @param {AnimationOptions} [options] Animation options.
  2278. * @param {Function} [complete] Function to perform at the end of animation.
  2279. *
  2280. * @sample highcharts/members/element-on/
  2281. * Setting some attributes by animation
  2282. *
  2283. * @returns {Highcharts.SVGElement} Returns the SVGElement for chaining.
  2284. */
  2285. animate: function(params, options, complete) {
  2286. var animOptions = H.animObject(
  2287. pick(options, this.renderer.globalAnimation, true)
  2288. );
  2289. if (animOptions.duration !== 0) {
  2290. if (complete) { // allows using a callback with the global animation without overwriting it
  2291. animOptions.complete = complete;
  2292. }
  2293. animate(this, params, animOptions);
  2294. } else {
  2295. this.attr(params, null, complete);
  2296. if (animOptions.step) {
  2297. animOptions.step.call(this);
  2298. }
  2299. }
  2300. return this;
  2301. },
  2302. /**
  2303. * @typedef {Object} GradientOptions
  2304. * @property {Object} linearGradient Holds an object that defines the start
  2305. * position and the end position relative to the shape.
  2306. * @property {Number} linearGradient.x1 Start horizontal position of the
  2307. * gradient. Ranges 0-1.
  2308. * @property {Number} linearGradient.x2 End horizontal position of the
  2309. * gradient. Ranges 0-1.
  2310. * @property {Number} linearGradient.y1 Start vertical position of the
  2311. * gradient. Ranges 0-1.
  2312. * @property {Number} linearGradient.y2 End vertical position of the
  2313. * gradient. Ranges 0-1.
  2314. * @property {Object} radialGradient Holds an object that defines the center
  2315. * position and the radius.
  2316. * @property {Number} radialGradient.cx Center horizontal position relative
  2317. * to the shape. Ranges 0-1.
  2318. * @property {Number} radialGradient.cy Center vertical position relative
  2319. * to the shape. Ranges 0-1.
  2320. * @property {Number} radialGradient.r Radius relative to the shape. Ranges
  2321. * 0-1.
  2322. * @property {Array.<Array>} stops The first item in each tuple is the
  2323. * position in the gradient, where 0 is the start of the gradient and 1
  2324. * is the end of the gradient. Multiple stops can be applied. The second
  2325. * item is the color for each stop. This color can also be given in the
  2326. * rgba format.
  2327. *
  2328. * @example
  2329. * // Linear gradient used as a color option
  2330. * color: {
  2331. * linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
  2332. * stops: [
  2333. * [0, '#003399'], // start
  2334. * [0.5, '#ffffff'], // middle
  2335. * [1, '#3366AA'] // end
  2336. * ]
  2337. * }
  2338. * }
  2339. */
  2340. /**
  2341. * Build and apply an SVG gradient out of a common JavaScript configuration
  2342. * object. This function is called from the attribute setters.
  2343. *
  2344. * @private
  2345. * @param {GradientOptions} color The gradient options structure.
  2346. * @param {string} prop The property to apply, can either be `fill` or
  2347. * `stroke`.
  2348. * @param {SVGDOMElement} elem SVG DOM element to apply the gradient on.
  2349. */
  2350. colorGradient: function(color, prop, elem) {
  2351. var renderer = this.renderer,
  2352. colorObject,
  2353. gradName,
  2354. gradAttr,
  2355. radAttr,
  2356. gradients,
  2357. gradientObject,
  2358. stops,
  2359. stopColor,
  2360. stopOpacity,
  2361. radialReference,
  2362. id,
  2363. key = [],
  2364. value;
  2365. // Apply linear or radial gradients
  2366. if (color.radialGradient) {
  2367. gradName = 'radialGradient';
  2368. } else if (color.linearGradient) {
  2369. gradName = 'linearGradient';
  2370. }
  2371. if (gradName) {
  2372. gradAttr = color[gradName];
  2373. gradients = renderer.gradients;
  2374. stops = color.stops;
  2375. radialReference = elem.radialReference;
  2376. // Keep < 2.2 kompatibility
  2377. if (isArray(gradAttr)) {
  2378. color[gradName] = gradAttr = {
  2379. x1: gradAttr[0],
  2380. y1: gradAttr[1],
  2381. x2: gradAttr[2],
  2382. y2: gradAttr[3],
  2383. gradientUnits: 'userSpaceOnUse'
  2384. };
  2385. }
  2386. // Correct the radial gradient for the radial reference system
  2387. if (
  2388. gradName === 'radialGradient' &&
  2389. radialReference &&
  2390. !defined(gradAttr.gradientUnits)
  2391. ) {
  2392. radAttr = gradAttr; // Save the radial attributes for updating
  2393. gradAttr = merge(
  2394. gradAttr,
  2395. renderer.getRadialAttr(radialReference, radAttr), {
  2396. gradientUnits: 'userSpaceOnUse'
  2397. }
  2398. );
  2399. }
  2400. // Build the unique key to detect whether we need to create a new element (#1282)
  2401. objectEach(gradAttr, function(val, n) {
  2402. if (n !== 'id') {
  2403. key.push(n, val);
  2404. }
  2405. });
  2406. objectEach(stops, function(val) {
  2407. key.push(val);
  2408. });
  2409. key = key.join(',');
  2410. // Check if a gradient object with the same config object is created within this renderer
  2411. if (gradients[key]) {
  2412. id = gradients[key].attr('id');
  2413. } else {
  2414. // Set the id and create the element
  2415. gradAttr.id = id = H.uniqueKey();
  2416. gradients[key] = gradientObject = renderer.createElement(gradName)
  2417. .attr(gradAttr)
  2418. .add(renderer.defs);
  2419. gradientObject.radAttr = radAttr;
  2420. // The gradient needs to keep a list of stops to be able to destroy them
  2421. gradientObject.stops = [];
  2422. each(stops, function(stop) {
  2423. var stopObject;
  2424. if (stop[1].indexOf('rgba') === 0) {
  2425. colorObject = H.color(stop[1]);
  2426. stopColor = colorObject.get('rgb');
  2427. stopOpacity = colorObject.get('a');
  2428. } else {
  2429. stopColor = stop[1];
  2430. stopOpacity = 1;
  2431. }
  2432. stopObject = renderer.createElement('stop').attr({
  2433. offset: stop[0],
  2434. 'stop-color': stopColor,
  2435. 'stop-opacity': stopOpacity
  2436. }).add(gradientObject);
  2437. // Add the stop element to the gradient
  2438. gradientObject.stops.push(stopObject);
  2439. });
  2440. }
  2441. // Set the reference to the gradient object
  2442. value = 'url(' + renderer.url + '#' + id + ')';
  2443. elem.setAttribute(prop, value);
  2444. elem.gradient = key;
  2445. // Allow the color to be concatenated into tooltips formatters etc. (#2995)
  2446. color.toString = function() {
  2447. return value;
  2448. };
  2449. }
  2450. },
  2451. /**
  2452. * Apply a text outline through a custom CSS property, by copying the text
  2453. * element and apply stroke to the copy. Used internally. Contrast checks
  2454. * at http://jsfiddle.net/highcharts/43soe9m1/2/ .
  2455. *
  2456. * @private
  2457. * @param {String} textOutline A custom CSS `text-outline` setting, defined
  2458. * by `width color`.
  2459. * @example
  2460. * // Specific color
  2461. * text.css({
  2462. * textOutline: '1px black'
  2463. * });
  2464. * // Automatic contrast
  2465. * text.css({
  2466. * color: '#000000', // black text
  2467. * textOutline: '1px contrast' // => white outline
  2468. * });
  2469. */
  2470. applyTextOutline: function(textOutline) {
  2471. var elem = this.element,
  2472. tspans,
  2473. tspan,
  2474. hasContrast = textOutline.indexOf('contrast') !== -1,
  2475. styles = {},
  2476. color,
  2477. strokeWidth,
  2478. firstRealChild,
  2479. i;
  2480. // When the text shadow is set to contrast, use dark stroke for light
  2481. // text and vice versa.
  2482. if (hasContrast) {
  2483. styles.textOutline = textOutline = textOutline.replace(
  2484. /contrast/g,
  2485. this.renderer.getContrast(elem.style.fill)
  2486. );
  2487. }
  2488. // Extract the stroke width and color
  2489. textOutline = textOutline.split(' ');
  2490. color = textOutline[textOutline.length - 1];
  2491. strokeWidth = textOutline[0];
  2492. if (strokeWidth && strokeWidth !== 'none' && H.svg) {
  2493. this.fakeTS = true; // Fake text shadow
  2494. tspans = [].slice.call(elem.getElementsByTagName('tspan'));
  2495. // In order to get the right y position of the clone,
  2496. // copy over the y setter
  2497. this.ySetter = this.xSetter;
  2498. // Since the stroke is applied on center of the actual outline, we
  2499. // need to double it to get the correct stroke-width outside the
  2500. // glyphs.
  2501. strokeWidth = strokeWidth.replace(
  2502. /(^[\d\.]+)(.*?)$/g,
  2503. function(match, digit, unit) {
  2504. return (2 * digit) + unit;
  2505. }
  2506. );
  2507. // Remove shadows from previous runs. Iterate from the end to
  2508. // support removing items inside the cycle (#6472).
  2509. i = tspans.length;
  2510. while (i--) {
  2511. tspan = tspans[i];
  2512. if (tspan.getAttribute('class') === 'highcharts-text-outline') {
  2513. // Remove then erase
  2514. erase(tspans, elem.removeChild(tspan));
  2515. }
  2516. }
  2517. // For each of the tspans, create a stroked copy behind it.
  2518. firstRealChild = elem.firstChild;
  2519. each(tspans, function(tspan, y) {
  2520. var clone;
  2521. // Let the first line start at the correct X position
  2522. if (y === 0) {
  2523. tspan.setAttribute('x', elem.getAttribute('x'));
  2524. y = elem.getAttribute('y');
  2525. tspan.setAttribute('y', y || 0);
  2526. if (y === null) {
  2527. elem.setAttribute('y', 0);
  2528. }
  2529. }
  2530. // Create the clone and apply outline properties
  2531. clone = tspan.cloneNode(1);
  2532. attr(clone, {
  2533. 'class': 'highcharts-text-outline',
  2534. 'fill': color,
  2535. 'stroke': color,
  2536. 'stroke-width': strokeWidth,
  2537. 'stroke-linejoin': 'round'
  2538. });
  2539. elem.insertBefore(clone, firstRealChild);
  2540. });
  2541. }
  2542. },
  2543. /**
  2544. *
  2545. * @typedef {Object} SVGAttributes An object of key-value pairs for SVG
  2546. * attributes. Attributes in Highcharts elements for the most parts
  2547. * correspond to SVG, but some are specific to Highcharts, like `zIndex`,
  2548. * `rotation`, `translateX`, `translateY`, `scaleX` and `scaleY`. SVG
  2549. * attributes containing a hyphen are _not_ camel-cased, they should be
  2550. * quoted to preserve the hyphen.
  2551. * @example
  2552. * {
  2553. * 'stroke': '#ff0000', // basic
  2554. * 'stroke-width': 2, // hyphenated
  2555. * 'rotation': 45 // custom
  2556. * 'd': ['M', 10, 10, 'L', 30, 30, 'z'] // path definition, note format
  2557. * }
  2558. */
  2559. /**
  2560. * Apply native and custom attributes to the SVG elements.
  2561. *
  2562. * In order to set the rotation center for rotation, set x and y to 0 and
  2563. * use `translateX` and `translateY` attributes to position the element
  2564. * instead.
  2565. *
  2566. * Attributes frequently used in Highcharts are `fill`, `stroke`,
  2567. * `stroke-width`.
  2568. *
  2569. * @param {SVGAttributes|String} hash - The native and custom SVG
  2570. * attributes.
  2571. * @param {string} [val] - If the type of the first argument is `string`,
  2572. * the second can be a value, which will serve as a single attribute
  2573. * setter. If the first argument is a string and the second is undefined,
  2574. * the function serves as a getter and the current value of the property
  2575. * is returned.
  2576. * @param {Function} [complete] - A callback function to execute after setting
  2577. * the attributes. This makes the function compliant and interchangeable
  2578. * with the {@link SVGElement#animate} function.
  2579. * @param {boolean} [continueAnimation=true] Used internally when `.attr` is
  2580. * called as part of an animation step. Otherwise, calling `.attr` for an
  2581. * attribute will stop animation for that attribute.
  2582. *
  2583. * @returns {SVGElement|string|number} If used as a setter, it returns the
  2584. * current {@link SVGElement} so the calls can be chained. If used as a
  2585. * getter, the current value of the attribute is returned.
  2586. *
  2587. * @sample highcharts/members/renderer-rect/
  2588. * Setting some attributes
  2589. *
  2590. * @example
  2591. * // Set multiple attributes
  2592. * element.attr({
  2593. * stroke: 'red',
  2594. * fill: 'blue',
  2595. * x: 10,
  2596. * y: 10
  2597. * });
  2598. *
  2599. * // Set a single attribute
  2600. * element.attr('stroke', 'red');
  2601. *
  2602. * // Get an attribute
  2603. * element.attr('stroke'); // => 'red'
  2604. *
  2605. */
  2606. attr: function(hash, val, complete, continueAnimation) {
  2607. var key,
  2608. element = this.element,
  2609. hasSetSymbolSize,
  2610. ret = this,
  2611. skipAttr,
  2612. setter;
  2613. // single key-value pair
  2614. if (typeof hash === 'string' && val !== undefined) {
  2615. key = hash;
  2616. hash = {};
  2617. hash[key] = val;
  2618. }
  2619. // used as a getter: first argument is a string, second is undefined
  2620. if (typeof hash === 'string') {
  2621. ret = (this[hash + 'Getter'] || this._defaultGetter).call(this, hash, element);
  2622. // setter
  2623. } else {
  2624. objectEach(hash, function(val, key) {
  2625. skipAttr = false;
  2626. // Unless .attr is from the animator update, stop current
  2627. // running animation of this property
  2628. if (!continueAnimation) {
  2629. stop(this, key);
  2630. }
  2631. // Special handling of symbol attributes
  2632. if (
  2633. this.symbolName &&
  2634. /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)$/
  2635. .test(key)
  2636. ) {
  2637. if (!hasSetSymbolSize) {
  2638. this.symbolAttr(hash);
  2639. hasSetSymbolSize = true;
  2640. }
  2641. skipAttr = true;
  2642. }
  2643. if (this.rotation && (key === 'x' || key === 'y')) {
  2644. this.doTransform = true;
  2645. }
  2646. if (!skipAttr) {
  2647. setter = this[key + 'Setter'] || this._defaultSetter;
  2648. setter.call(this, val, key, element);
  2649. // Let the shadow follow the main element
  2650. if (this.shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {
  2651. this.updateShadows(key, val, setter);
  2652. }
  2653. }
  2654. }, this);
  2655. this.afterSetters();
  2656. }
  2657. // In accordance with animate, run a complete callback
  2658. if (complete) {
  2659. complete();
  2660. }
  2661. return ret;
  2662. },
  2663. /**
  2664. * This method is executed in the end of {attr}, after setting all attributes in the hash.
  2665. * In can be used to efficiently consolidate multiple attributes in one SVG property -- e.g.,
  2666. * translate, rotate and scale are merged in one "transform" attribute in the SVG node.
  2667. */
  2668. afterSetters: function() {
  2669. // Update transform. Do this outside the loop to prevent redundant updating for batch setting
  2670. // of attributes.
  2671. if (this.doTransform) {
  2672. this.updateTransform();
  2673. this.doTransform = false;
  2674. }
  2675. },
  2676. /**
  2677. * Update the shadow elements with new attributes.
  2678. *
  2679. * @private
  2680. * @param {String} key - The attribute name.
  2681. * @param {String|Number} value - The value of the attribute.
  2682. * @param {Function} setter - The setter function, inherited from the
  2683. * parent wrapper
  2684. * @returns {void}
  2685. */
  2686. updateShadows: function(key, value, setter) {
  2687. var shadows = this.shadows,
  2688. i = shadows.length;
  2689. while (i--) {
  2690. setter.call(
  2691. shadows[i],
  2692. key === 'height' ?
  2693. Math.max(value - (shadows[i].cutHeight || 0), 0) :
  2694. key === 'd' ? this.d : value,
  2695. key,
  2696. shadows[i]
  2697. );
  2698. }
  2699. },
  2700. /**
  2701. * Add a class name to an element.
  2702. *
  2703. * @param {string} className - The new class name to add.
  2704. * @param {boolean} [replace=false] - When true, the existing class name(s)
  2705. * will be overwritten with the new one. When false, the new one is
  2706. * added.
  2707. * @returns {Highcharts.SVGElement} Return the SVG element for chainability.
  2708. */
  2709. addClass: function(className, replace) {
  2710. var currentClassName = this.attr('class') || '';
  2711. if (currentClassName.indexOf(className) === -1) {
  2712. if (!replace) {
  2713. className =
  2714. (currentClassName + (currentClassName ? ' ' : '') +
  2715. className).replace(' ', ' ');
  2716. }
  2717. this.attr('class', className);
  2718. }
  2719. return this;
  2720. },
  2721. /**
  2722. * Check if an element has the given class name.
  2723. * @param {string} className - The class name to check for.
  2724. * @return {Boolean}
  2725. */
  2726. hasClass: function(className) {
  2727. return attr(this.element, 'class').indexOf(className) !== -1;
  2728. },
  2729. /**
  2730. * Remove a class name from the element.
  2731. * @param {string} className The class name to remove.
  2732. * @return {Highcharts.SVGElement} Returns the SVG element for chainability.
  2733. */
  2734. removeClass: function(className) {
  2735. attr(this.element, 'class', (attr(this.element, 'class') || '').replace(className, ''));
  2736. return this;
  2737. },
  2738. /**
  2739. * If one of the symbol size affecting parameters are changed,
  2740. * check all the others only once for each call to an element's
  2741. * .attr() method
  2742. * @param {Object} hash - The attributes to set.
  2743. * @private
  2744. */
  2745. symbolAttr: function(hash) {
  2746. var wrapper = this;
  2747. each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function(key) {
  2748. wrapper[key] = pick(hash[key], wrapper[key]);
  2749. });
  2750. wrapper.attr({
  2751. d: wrapper.renderer.symbols[wrapper.symbolName](
  2752. wrapper.x,
  2753. wrapper.y,
  2754. wrapper.width,
  2755. wrapper.height,
  2756. wrapper
  2757. )
  2758. });
  2759. },
  2760. /**
  2761. * Apply a clipping rectangle to this element.
  2762. *
  2763. * @param {ClipRect} [clipRect] - The clipping rectangle. If skipped, the
  2764. * current clip is removed.
  2765. * @returns {Highcharts.SVGElement} Returns the SVG element to allow chaining.
  2766. */
  2767. clip: function(clipRect) {
  2768. return this.attr(
  2769. 'clip-path',
  2770. clipRect ?
  2771. 'url(' + this.renderer.url + '#' + clipRect.id + ')' :
  2772. 'none'
  2773. );
  2774. },
  2775. /**
  2776. * Calculate the coordinates needed for drawing a rectangle crisply and
  2777. * return the calculated attributes.
  2778. *
  2779. * @param {Object} rect - A rectangle.
  2780. * @param {number} rect.x - The x position.
  2781. * @param {number} rect.y - The y position.
  2782. * @param {number} rect.width - The width.
  2783. * @param {number} rect.height - The height.
  2784. * @param {number} [strokeWidth] - The stroke width to consider when
  2785. * computing crisp positioning. It can also be set directly on the rect
  2786. * parameter.
  2787. *
  2788. * @returns {{x: Number, y: Number, width: Number, height: Number}} The
  2789. * modified rectangle arguments.
  2790. */
  2791. crisp: function(rect, strokeWidth) {
  2792. var wrapper = this,
  2793. attribs = {},
  2794. normalizer;
  2795. strokeWidth = strokeWidth || rect.strokeWidth || 0;
  2796. normalizer = Math.round(strokeWidth) % 2 / 2; // Math.round because strokeWidth can sometimes have roundoff errors
  2797. // normalize for crisp edges
  2798. rect.x = Math.floor(rect.x || wrapper.x || 0) + normalizer;
  2799. rect.y = Math.floor(rect.y || wrapper.y || 0) + normalizer;
  2800. rect.width = Math.floor((rect.width || wrapper.width || 0) - 2 * normalizer);
  2801. rect.height = Math.floor((rect.height || wrapper.height || 0) - 2 * normalizer);
  2802. if (defined(rect.strokeWidth)) {
  2803. rect.strokeWidth = strokeWidth;
  2804. }
  2805. objectEach(rect, function(val, key) {
  2806. if (wrapper[key] !== val) { // only set attribute if changed
  2807. wrapper[key] = attribs[key] = val;
  2808. }
  2809. });
  2810. return attribs;
  2811. },
  2812. /**
  2813. * Set styles for the element. In addition to CSS styles supported by
  2814. * native SVG and HTML elements, there are also some custom made for
  2815. * Highcharts, like `width`, `ellipsis` and `textOverflow` for SVG text
  2816. * elements.
  2817. * @param {CSSObject} styles The new CSS styles.
  2818. * @returns {Highcharts.SVGElement} Return the SVG element for chaining.
  2819. *
  2820. * @sample highcharts/members/renderer-text-on-chart/
  2821. * Styled text
  2822. */
  2823. css: function(styles) {
  2824. var oldStyles = this.styles,
  2825. newStyles = {},
  2826. elem = this.element,
  2827. textWidth,
  2828. serializedCss = '',
  2829. hyphenate,
  2830. hasNew = !oldStyles,
  2831. // These CSS properties are interpreted internally by the SVG
  2832. // renderer, but are not supported by SVG and should not be added to
  2833. // the DOM. In styled mode, no CSS should find its way to the DOM
  2834. // whatsoever (#6173, #6474).
  2835. svgPseudoProps = ['textOutline', 'textOverflow', 'width'];
  2836. // convert legacy
  2837. if (styles && styles.color) {
  2838. styles.fill = styles.color;
  2839. }
  2840. // Filter out existing styles to increase performance (#2640)
  2841. if (oldStyles) {
  2842. objectEach(styles, function(style, n) {
  2843. if (style !== oldStyles[n]) {
  2844. newStyles[n] = style;
  2845. hasNew = true;
  2846. }
  2847. });
  2848. }
  2849. if (hasNew) {
  2850. // Merge the new styles with the old ones
  2851. if (oldStyles) {
  2852. styles = extend(
  2853. oldStyles,
  2854. newStyles
  2855. );
  2856. }
  2857. // Get the text width from style
  2858. textWidth = this.textWidth = (
  2859. styles &&
  2860. styles.width &&
  2861. styles.width !== 'auto' &&
  2862. elem.nodeName.toLowerCase() === 'text' &&
  2863. pInt(styles.width)
  2864. );
  2865. // store object
  2866. this.styles = styles;
  2867. if (textWidth && (!svg && this.renderer.forExport)) {
  2868. delete styles.width;
  2869. }
  2870. // serialize and set style attribute
  2871. if (isMS && !svg) {
  2872. css(this.element, styles);
  2873. } else {
  2874. hyphenate = function(a, b) {
  2875. return '-' + b.toLowerCase();
  2876. };
  2877. objectEach(styles, function(style, n) {
  2878. if (inArray(n, svgPseudoProps) === -1) {
  2879. serializedCss +=
  2880. n.replace(/([A-Z])/g, hyphenate) + ':' +
  2881. style + ';';
  2882. }
  2883. });
  2884. if (serializedCss) {
  2885. attr(elem, 'style', serializedCss); // #1881
  2886. }
  2887. }
  2888. if (this.added) {
  2889. // Rebuild text after added. Cache mechanisms in the buildText
  2890. // will prevent building if there are no significant changes.
  2891. if (this.element.nodeName === 'text') {
  2892. this.renderer.buildText(this);
  2893. }
  2894. // Apply text outline after added
  2895. if (styles && styles.textOutline) {
  2896. this.applyTextOutline(styles.textOutline);
  2897. }
  2898. }
  2899. }
  2900. return this;
  2901. },
  2902. /**
  2903. * Get the current stroke width. In classic mode, the setter registers it
  2904. * directly on the element.
  2905. * @returns {number} The stroke width in pixels.
  2906. * @ignore
  2907. */
  2908. strokeWidth: function() {
  2909. return this['stroke-width'] || 0;
  2910. },
  2911. /**
  2912. * Add an event listener. This is a simple setter that replaces all other
  2913. * events of the same type, opposed to the {@link Highcharts#addEvent}
  2914. * function.
  2915. * @param {string} eventType - The event type. If the type is `click`,
  2916. * Highcharts will internally translate it to a `touchstart` event on
  2917. * touch devices, to prevent the browser from waiting for a click event
  2918. * from firing.
  2919. * @param {Function} handler - The handler callback.
  2920. * @returns {Highcharts.SVGElement} The SVGElement for chaining.
  2921. *
  2922. * @sample highcharts/members/element-on/
  2923. * A clickable rectangle
  2924. */
  2925. on: function(eventType, handler) {
  2926. var svgElement = this,
  2927. element = svgElement.element;
  2928. // touch
  2929. if (hasTouch && eventType === 'click') {
  2930. element.ontouchstart = function(e) {
  2931. svgElement.touchEventFired = Date.now(); // #2269
  2932. e.preventDefault();
  2933. handler.call(element, e);
  2934. };
  2935. element.onclick = function(e) {
  2936. if (win.navigator.userAgent.indexOf('Android') === -1 ||
  2937. Date.now() - (svgElement.touchEventFired || 0) > 1100) {
  2938. handler.call(element, e);
  2939. }
  2940. };
  2941. } else {
  2942. // simplest possible event model for internal use
  2943. element['on' + eventType] = handler;
  2944. }
  2945. return this;
  2946. },
  2947. /**
  2948. * Set the coordinates needed to draw a consistent radial gradient across
  2949. * a shape regardless of positioning inside the chart. Used on pie slices
  2950. * to make all the slices have the same radial reference point.
  2951. *
  2952. * @param {Array} coordinates The center reference. The format is
  2953. * `[centerX, centerY, diameter]` in pixels.
  2954. * @returns {Highcharts.SVGElement} Returns the SVGElement for chaining.
  2955. */
  2956. setRadialReference: function(coordinates) {
  2957. var existingGradient = this.renderer.gradients[this.element.gradient];
  2958. this.element.radialReference = coordinates;
  2959. // On redrawing objects with an existing gradient, the gradient needs
  2960. // to be repositioned (#3801)
  2961. if (existingGradient && existingGradient.radAttr) {
  2962. existingGradient.animate(
  2963. this.renderer.getRadialAttr(
  2964. coordinates,
  2965. existingGradient.radAttr
  2966. )
  2967. );
  2968. }
  2969. return this;
  2970. },
  2971. /**
  2972. * Move an object and its children by x and y values.
  2973. *
  2974. * @param {number} x - The x value.
  2975. * @param {number} y - The y value.
  2976. */
  2977. translate: function(x, y) {
  2978. return this.attr({
  2979. translateX: x,
  2980. translateY: y
  2981. });
  2982. },
  2983. /**
  2984. * Invert a group, rotate and flip. This is used internally on inverted
  2985. * charts, where the points and graphs are drawn as if not inverted, then
  2986. * the series group elements are inverted.
  2987. *
  2988. * @param {boolean} inverted - Whether to invert or not. An inverted shape
  2989. * can be un-inverted by setting it to false.
  2990. * @returns {Highcharts.SVGElement} Return the SVGElement for chaining.
  2991. */
  2992. invert: function(inverted) {
  2993. var wrapper = this;
  2994. wrapper.inverted = inverted;
  2995. wrapper.updateTransform();
  2996. return wrapper;
  2997. },
  2998. /**
  2999. * Update the transform attribute based on internal properties. Deals with
  3000. * the custom `translateX`, `translateY`, `rotation`, `scaleX` and `scaleY`
  3001. * attributes and updates the SVG `transform` attribute.
  3002. * @private
  3003. * @returns {void}
  3004. */
  3005. updateTransform: function() {
  3006. var wrapper = this,
  3007. translateX = wrapper.translateX || 0,
  3008. translateY = wrapper.translateY || 0,
  3009. scaleX = wrapper.scaleX,
  3010. scaleY = wrapper.scaleY,
  3011. inverted = wrapper.inverted,
  3012. rotation = wrapper.rotation,
  3013. element = wrapper.element,
  3014. transform;
  3015. // flipping affects translate as adjustment for flipping around the group's axis
  3016. if (inverted) {
  3017. translateX += wrapper.width;
  3018. translateY += wrapper.height;
  3019. }
  3020. // Apply translate. Nearly all transformed elements have translation, so instead
  3021. // of checking for translate = 0, do it always (#1767, #1846).
  3022. transform = ['translate(' + translateX + ',' + translateY + ')'];
  3023. // apply rotation
  3024. if (inverted) {
  3025. transform.push('rotate(90) scale(-1,1)');
  3026. } else if (rotation) { // text rotation
  3027. transform.push('rotate(' + rotation + ' ' + (element.getAttribute('x') || 0) + ' ' + (element.getAttribute('y') || 0) + ')');
  3028. // Delete bBox memo when the rotation changes
  3029. //delete wrapper.bBox;
  3030. }
  3031. // apply scale
  3032. if (defined(scaleX) || defined(scaleY)) {
  3033. transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')');
  3034. }
  3035. if (transform.length) {
  3036. element.setAttribute('transform', transform.join(' '));
  3037. }
  3038. },
  3039. /**
  3040. * Bring the element to the front. Alternatively, a new zIndex can be set.
  3041. *
  3042. * @returns {Highcharts.SVGElement} Returns the SVGElement for chaining.
  3043. *
  3044. * @sample highcharts/members/element-tofront/
  3045. * Click an element to bring it to front
  3046. */
  3047. toFront: function() {
  3048. var element = this.element;
  3049. element.parentNode.appendChild(element);
  3050. return this;
  3051. },
  3052. /**
  3053. * Align the element relative to the chart or another box.
  3054. *
  3055. * @param {Object} [alignOptions] The alignment options. The function can be
  3056. * called without this parameter in order to re-align an element after the
  3057. * box has been updated.
  3058. * @param {string} [alignOptions.align=left] Horizontal alignment. Can be
  3059. * one of `left`, `center` and `right`.
  3060. * @param {string} [alignOptions.verticalAlign=top] Vertical alignment. Can
  3061. * be one of `top`, `middle` and `bottom`.
  3062. * @param {number} [alignOptions.x=0] Horizontal pixel offset from
  3063. * alignment.
  3064. * @param {number} [alignOptions.y=0] Vertical pixel offset from alignment.
  3065. * @param {Boolean} [alignByTranslate=false] Use the `transform` attribute
  3066. * with translateX and translateY custom attributes to align this elements
  3067. * rather than `x` and `y` attributes.
  3068. * @param {String|Object} box The box to align to, needs a width and height.
  3069. * When the box is a string, it refers to an object in the Renderer. For
  3070. * example, when box is `spacingBox`, it refers to `Renderer.spacingBox`
  3071. * which holds `width`, `height`, `x` and `y` properties.
  3072. * @returns {Highcharts.SVGElement} Returns the SVGElement for chaining.
  3073. */
  3074. align: function(alignOptions, alignByTranslate, box) {
  3075. var align,
  3076. vAlign,
  3077. x,
  3078. y,
  3079. attribs = {},
  3080. alignTo,
  3081. renderer = this.renderer,
  3082. alignedObjects = renderer.alignedObjects,
  3083. alignFactor,
  3084. vAlignFactor;
  3085. // First call on instanciate
  3086. if (alignOptions) {
  3087. this.alignOptions = alignOptions;
  3088. this.alignByTranslate = alignByTranslate;
  3089. if (!box || isString(box)) { // boxes other than renderer handle this internally
  3090. this.alignTo = alignTo = box || 'renderer';
  3091. erase(alignedObjects, this); // prevent duplicates, like legendGroup after resize
  3092. alignedObjects.push(this);
  3093. box = null; // reassign it below
  3094. }
  3095. // When called on resize, no arguments are supplied
  3096. } else {
  3097. alignOptions = this.alignOptions;
  3098. alignByTranslate = this.alignByTranslate;
  3099. alignTo = this.alignTo;
  3100. }
  3101. box = pick(box, renderer[alignTo], renderer);
  3102. // Assign variables
  3103. align = alignOptions.align;
  3104. vAlign = alignOptions.verticalAlign;
  3105. x = (box.x || 0) + (alignOptions.x || 0); // default: left align
  3106. y = (box.y || 0) + (alignOptions.y || 0); // default: top align
  3107. // Align
  3108. if (align === 'right') {
  3109. alignFactor = 1;
  3110. } else if (align === 'center') {
  3111. alignFactor = 2;
  3112. }
  3113. if (alignFactor) {
  3114. x += (box.width - (alignOptions.width || 0)) / alignFactor;
  3115. }
  3116. attribs[alignByTranslate ? 'translateX' : 'x'] = Math.round(x);
  3117. // Vertical align
  3118. if (vAlign === 'bottom') {
  3119. vAlignFactor = 1;
  3120. } else if (vAlign === 'middle') {
  3121. vAlignFactor = 2;
  3122. }
  3123. if (vAlignFactor) {
  3124. y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
  3125. }
  3126. attribs[alignByTranslate ? 'translateY' : 'y'] = Math.round(y);
  3127. // Animate only if already placed
  3128. this[this.placed ? 'animate' : 'attr'](attribs);
  3129. this.placed = true;
  3130. this.alignAttr = attribs;
  3131. return this;
  3132. },
  3133. /**
  3134. * Get the bounding box (width, height, x and y) for the element. Generally
  3135. * used to get rendered text size. Since this is called a lot in charts,
  3136. * the results are cached based on text properties, in order to save DOM
  3137. * traffic. The returned bounding box includes the rotation, so for example
  3138. * a single text line of rotation 90 will report a greater height, and a
  3139. * width corresponding to the line-height.
  3140. *
  3141. * @param {boolean} [reload] Skip the cache and get the updated DOM bouding
  3142. * box.
  3143. * @param {number} [rot] Override the element's rotation. This is internally
  3144. * used on axis labels with a value of 0 to find out what the bounding box
  3145. * would be have been if it were not rotated.
  3146. * @returns {Object} The bounding box with `x`, `y`, `width` and `height`
  3147. * properties.
  3148. *
  3149. * @sample highcharts/members/renderer-on-chart/
  3150. * Draw a rectangle based on a text's bounding box
  3151. */
  3152. getBBox: function(reload, rot) {
  3153. var wrapper = this,
  3154. bBox, // = wrapper.bBox,
  3155. renderer = wrapper.renderer,
  3156. width,
  3157. height,
  3158. rotation,
  3159. rad,
  3160. element = wrapper.element,
  3161. styles = wrapper.styles,
  3162. fontSize,
  3163. textStr = wrapper.textStr,
  3164. toggleTextShadowShim,
  3165. cache = renderer.cache,
  3166. cacheKeys = renderer.cacheKeys,
  3167. cacheKey;
  3168. rotation = pick(rot, wrapper.rotation);
  3169. rad = rotation * deg2rad;
  3170. fontSize = styles && styles.fontSize;
  3171. if (textStr !== undefined) {
  3172. cacheKey = textStr.toString();
  3173. // Since numbers are monospaced, and numerical labels appear a lot
  3174. // in a chart, we assume that a label of n characters has the same
  3175. // bounding box as others of the same length. Unless there is inner
  3176. // HTML in the label. In that case, leave the numbers as is (#5899).
  3177. if (cacheKey.indexOf('<') === -1) {
  3178. cacheKey = cacheKey.replace(/[0-9]/g, '0');
  3179. }
  3180. // Properties that affect bounding box
  3181. cacheKey += [
  3182. '',
  3183. rotation || 0,
  3184. fontSize,
  3185. styles && styles.width,
  3186. styles && styles.textOverflow // #5968
  3187. ]
  3188. .join(',');
  3189. }
  3190. if (cacheKey && !reload) {
  3191. bBox = cache[cacheKey];
  3192. }
  3193. // No cache found
  3194. if (!bBox) {
  3195. // SVG elements
  3196. if (element.namespaceURI === wrapper.SVG_NS || renderer.forExport) {
  3197. try { // Fails in Firefox if the container has display: none.
  3198. // When the text shadow shim is used, we need to hide the fake shadows
  3199. // to get the correct bounding box (#3872)
  3200. toggleTextShadowShim = this.fakeTS && function(display) {
  3201. each(element.querySelectorAll('.highcharts-text-outline'), function(tspan) {
  3202. tspan.style.display = display;
  3203. });
  3204. };
  3205. // Workaround for #3842, Firefox reporting wrong bounding box for shadows
  3206. if (toggleTextShadowShim) {
  3207. toggleTextShadowShim('none');
  3208. }
  3209. bBox = element.getBBox ?
  3210. // SVG: use extend because IE9 is not allowed to change width and height in case
  3211. // of rotation (below)
  3212. extend({}, element.getBBox()) : {
  3213. // Legacy IE in export mode
  3214. width: element.offsetWidth,
  3215. height: element.offsetHeight
  3216. };
  3217. // #3842
  3218. if (toggleTextShadowShim) {
  3219. toggleTextShadowShim('');
  3220. }
  3221. } catch (e) {}
  3222. // If the bBox is not set, the try-catch block above failed. The other condition
  3223. // is for Opera that returns a width of -Infinity on hidden elements.
  3224. if (!bBox || bBox.width < 0) {
  3225. bBox = {
  3226. width: 0,
  3227. height: 0
  3228. };
  3229. }
  3230. // VML Renderer or useHTML within SVG
  3231. } else {
  3232. bBox = wrapper.htmlGetBBox();
  3233. }
  3234. // True SVG elements as well as HTML elements in modern browsers using the .useHTML option
  3235. // need to compensated for rotation
  3236. if (renderer.isSVG) {
  3237. width = bBox.width;
  3238. height = bBox.height;
  3239. // Workaround for wrong bounding box in IE, Edge and Chrome on
  3240. // Windows. With Highcharts' default font, IE and Edge report
  3241. // a box height of 16.899 and Chrome rounds it to 17. If this
  3242. // stands uncorrected, it results in more padding added below
  3243. // the text than above when adding a label border or background.
  3244. // Also vertical positioning is affected.
  3245. // http://jsfiddle.net/highcharts/em37nvuj/
  3246. // (#1101, #1505, #1669, #2568, #6213).
  3247. if (
  3248. styles &&
  3249. styles.fontSize === '11px' &&
  3250. Math.round(height) === 17
  3251. ) {
  3252. bBox.height = height = 14;
  3253. }
  3254. // Adjust for rotated text
  3255. if (rotation) {
  3256. bBox.width = Math.abs(height * Math.sin(rad)) + Math.abs(width * Math.cos(rad));
  3257. bBox.height = Math.abs(height * Math.cos(rad)) + Math.abs(width * Math.sin(rad));
  3258. }
  3259. }
  3260. // Cache it. When loading a chart in a hidden iframe in Firefox and IE/Edge, the
  3261. // bounding box height is 0, so don't cache it (#5620).
  3262. if (cacheKey && bBox.height > 0) {
  3263. // Rotate (#4681)
  3264. while (cacheKeys.length > 250) {
  3265. delete cache[cacheKeys.shift()];
  3266. }
  3267. if (!cache[cacheKey]) {
  3268. cacheKeys.push(cacheKey);
  3269. }
  3270. cache[cacheKey] = bBox;
  3271. }
  3272. }
  3273. return bBox;
  3274. },
  3275. /**
  3276. * Show the element after it has been hidden.
  3277. *
  3278. * @param {boolean} [inherit=false] Set the visibility attribute to
  3279. * `inherit` rather than `visible`. The difference is that an element with
  3280. * `visibility="visible"` will be visible even if the parent is hidden.
  3281. *
  3282. * @returns {Highcharts.SVGElement} Returns the SVGElement for chaining.
  3283. */
  3284. show: function(inherit) {
  3285. return this.attr({
  3286. visibility: inherit ? 'inherit' : 'visible'
  3287. });
  3288. },
  3289. /**
  3290. * Hide the element, equivalent to setting the `visibility` attribute to
  3291. * `hidden`.
  3292. *
  3293. * @returns {Highcharts.SVGElement} Returns the SVGElement for chaining.
  3294. */
  3295. hide: function() {
  3296. return this.attr({
  3297. visibility: 'hidden'
  3298. });
  3299. },
  3300. /**
  3301. * Fade out an element by animating its opacity down to 0, and hide it on
  3302. * complete. Used internally for the tooltip.
  3303. *
  3304. * @param {number} [duration=150] The fade duration in milliseconds.
  3305. */
  3306. fadeOut: function(duration) {
  3307. var elemWrapper = this;
  3308. elemWrapper.animate({
  3309. opacity: 0
  3310. }, {
  3311. duration: duration || 150,
  3312. complete: function() {
  3313. elemWrapper.attr({
  3314. y: -9999
  3315. }); // #3088, assuming we're only using this for tooltips
  3316. }
  3317. });
  3318. },
  3319. /**
  3320. * Add the element to the DOM. All elements must be added this way.
  3321. *
  3322. * @param {Highcharts.SVGElement|SVGDOMElement} [parent] The parent item to add it to.
  3323. * If undefined, the element is added to the {@link
  3324. * Highcharts.SVGRenderer.box}.
  3325. *
  3326. * @returns {Highcharts.SVGElement} Returns the SVGElement for chaining.
  3327. *
  3328. * @sample highcharts/members/renderer-g - Elements added to a group
  3329. */
  3330. add: function(parent) {
  3331. var renderer = this.renderer,
  3332. element = this.element,
  3333. inserted;
  3334. if (parent) {
  3335. this.parentGroup = parent;
  3336. }
  3337. // mark as inverted
  3338. this.parentInverted = parent && parent.inverted;
  3339. // build formatted text
  3340. if (this.textStr !== undefined) {
  3341. renderer.buildText(this);
  3342. }
  3343. // Mark as added
  3344. this.added = true;
  3345. // If we're adding to renderer root, or other elements in the group
  3346. // have a z index, we need to handle it
  3347. if (!parent || parent.handleZ || this.zIndex) {
  3348. inserted = this.zIndexSetter();
  3349. }
  3350. // If zIndex is not handled, append at the end
  3351. if (!inserted) {
  3352. (parent ? parent.element : renderer.box).appendChild(element);
  3353. }
  3354. // fire an event for internal hooks
  3355. if (this.onAdd) {
  3356. this.onAdd();
  3357. }
  3358. return this;
  3359. },
  3360. /**
  3361. * Removes an element from the DOM.
  3362. *
  3363. * @private
  3364. * @param {SVGDOMElement|HTMLDOMElement} element The DOM node to remove.
  3365. */
  3366. safeRemoveChild: function(element) {
  3367. var parentNode = element.parentNode;
  3368. if (parentNode) {
  3369. parentNode.removeChild(element);
  3370. }
  3371. },
  3372. /**
  3373. * Destroy the element and element wrapper and clear up the DOM and event
  3374. * hooks.
  3375. *
  3376. * @returns {void}
  3377. */
  3378. destroy: function() {
  3379. var wrapper = this,
  3380. element = wrapper.element || {},
  3381. parentToClean =
  3382. wrapper.renderer.isSVG &&
  3383. element.nodeName === 'SPAN' &&
  3384. wrapper.parentGroup,
  3385. grandParent,
  3386. ownerSVGElement = element.ownerSVGElement,
  3387. i;
  3388. // remove events
  3389. element.onclick = element.onmouseout = element.onmouseover =
  3390. element.onmousemove = element.point = null;
  3391. stop(wrapper); // stop running animations
  3392. if (wrapper.clipPath && ownerSVGElement) {
  3393. // Look for existing references to this clipPath and remove them
  3394. // before destroying the element (#6196).
  3395. each(
  3396. ownerSVGElement.querySelectorAll('[clip-path]'),
  3397. function(el) {
  3398. // Include the closing paranthesis in the test to rule out
  3399. // id's from 10 and above (#6550)
  3400. if (el.getAttribute('clip-path')
  3401. .indexOf(wrapper.clipPath.element.id + ')') > -1) {
  3402. el.removeAttribute('clip-path');
  3403. }
  3404. }
  3405. );
  3406. wrapper.clipPath = wrapper.clipPath.destroy();
  3407. }
  3408. // Destroy stops in case this is a gradient object
  3409. if (wrapper.stops) {
  3410. for (i = 0; i < wrapper.stops.length; i++) {
  3411. wrapper.stops[i] = wrapper.stops[i].destroy();
  3412. }
  3413. wrapper.stops = null;
  3414. }
  3415. // remove element
  3416. wrapper.safeRemoveChild(element);
  3417. wrapper.destroyShadows();
  3418. // In case of useHTML, clean up empty containers emulating SVG groups (#1960, #2393, #2697).
  3419. while (parentToClean && parentToClean.div && parentToClean.div.childNodes.length === 0) {
  3420. grandParent = parentToClean.parentGroup;
  3421. wrapper.safeRemoveChild(parentToClean.div);
  3422. delete parentToClean.div;
  3423. parentToClean = grandParent;
  3424. }
  3425. // remove from alignObjects
  3426. if (wrapper.alignTo) {
  3427. erase(wrapper.renderer.alignedObjects, wrapper);
  3428. }
  3429. objectEach(wrapper, function(val, key) {
  3430. delete wrapper[key];
  3431. });
  3432. return null;
  3433. },
  3434. /**
  3435. * @typedef {Object} ShadowOptions
  3436. * @property {string} [color=#000000] The shadow color.
  3437. * @property {number} [offsetX=1] The horizontal offset from the element.
  3438. * @property {number} [offsetY=1] The vertical offset from the element.
  3439. * @property {number} [opacity=0.15] The shadow opacity.
  3440. * @property {number} [width=3] The shadow width or distance from the
  3441. * element.
  3442. */
  3443. /**
  3444. * Add a shadow to the element. Must be called after the element is added to
  3445. * the DOM. In styled mode, this method is not used, instead use `defs` and
  3446. * filters.
  3447. *
  3448. * @param {boolean|ShadowOptions} shadowOptions The shadow options. If
  3449. * `true`, the default options are applied. If `false`, the current
  3450. * shadow will be removed.
  3451. * @param {Highcharts.SVGElement} [group] The SVG group element where the shadows will
  3452. * be applied. The default is to add it to the same parent as the current
  3453. * element. Internally, this is ised for pie slices, where all the
  3454. * shadows are added to an element behind all the slices.
  3455. * @param {boolean} [cutOff] Used internally for column shadows.
  3456. *
  3457. * @returns {Highcharts.SVGElement} Returns the SVGElement for chaining.
  3458. *
  3459. * @example
  3460. * renderer.rect(10, 100, 100, 100)
  3461. * .attr({ fill: 'red' })
  3462. * .shadow(true);
  3463. */
  3464. shadow: function(shadowOptions, group, cutOff) {
  3465. var shadows = [],
  3466. i,
  3467. shadow,
  3468. element = this.element,
  3469. strokeWidth,
  3470. shadowWidth,
  3471. shadowElementOpacity,
  3472. // compensate for inverted plot area
  3473. transform;
  3474. if (!shadowOptions) {
  3475. this.destroyShadows();
  3476. } else if (!this.shadows) {
  3477. shadowWidth = pick(shadowOptions.width, 3);
  3478. shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
  3479. transform = this.parentInverted ?
  3480. '(-1,-1)' :
  3481. '(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')';
  3482. for (i = 1; i <= shadowWidth; i++) {
  3483. shadow = element.cloneNode(0);
  3484. strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
  3485. attr(shadow, {
  3486. 'isShadow': 'true',
  3487. 'stroke': shadowOptions.color || '#000000',
  3488. 'stroke-opacity': shadowElementOpacity * i,
  3489. 'stroke-width': strokeWidth,
  3490. 'transform': 'translate' + transform,
  3491. 'fill': 'none'
  3492. });
  3493. if (cutOff) {
  3494. attr(shadow, 'height', Math.max(attr(shadow, 'height') - strokeWidth, 0));
  3495. shadow.cutHeight = strokeWidth;
  3496. }
  3497. if (group) {
  3498. group.element.appendChild(shadow);
  3499. } else {
  3500. element.parentNode.insertBefore(shadow, element);
  3501. }
  3502. shadows.push(shadow);
  3503. }
  3504. this.shadows = shadows;
  3505. }
  3506. return this;
  3507. },
  3508. /**
  3509. * Destroy shadows on the element.
  3510. * @private
  3511. */
  3512. destroyShadows: function() {
  3513. each(this.shadows || [], function(shadow) {
  3514. this.safeRemoveChild(shadow);
  3515. }, this);
  3516. this.shadows = undefined;
  3517. },
  3518. xGetter: function(key) {
  3519. if (this.element.nodeName === 'circle') {
  3520. if (key === 'x') {
  3521. key = 'cx';
  3522. } else if (key === 'y') {
  3523. key = 'cy';
  3524. }
  3525. }
  3526. return this._defaultGetter(key);
  3527. },
  3528. /**
  3529. * Get the current value of an attribute or pseudo attribute, used mainly
  3530. * for animation. Called internally from the {@link
  3531. * Highcharts.SVGRenderer#attr}
  3532. * function.
  3533. *
  3534. * @private
  3535. */
  3536. _defaultGetter: function(key) {
  3537. var ret = pick(this[key], this.element ? this.element.getAttribute(key) : null, 0);
  3538. if (/^[\-0-9\.]+$/.test(ret)) { // is numerical
  3539. ret = parseFloat(ret);
  3540. }
  3541. return ret;
  3542. },
  3543. dSetter: function(value, key, element) {
  3544. if (value && value.join) { // join path
  3545. value = value.join(' ');
  3546. }
  3547. if (/(NaN| {2}|^$)/.test(value)) {
  3548. value = 'M 0 0';
  3549. }
  3550. element.setAttribute(key, value);
  3551. this[key] = value;
  3552. },
  3553. dashstyleSetter: function(value) {
  3554. var i,
  3555. strokeWidth = this['stroke-width'];
  3556. // If "inherit", like maps in IE, assume 1 (#4981). With HC5 and the new strokeWidth
  3557. // function, we should be able to use that instead.
  3558. if (strokeWidth === 'inherit') {
  3559. strokeWidth = 1;
  3560. }
  3561. value = value && value.toLowerCase();
  3562. if (value) {
  3563. value = value
  3564. .replace('shortdashdotdot', '3,1,1,1,1,1,')
  3565. .replace('shortdashdot', '3,1,1,1')
  3566. .replace('shortdot', '1,1,')
  3567. .replace('shortdash', '3,1,')
  3568. .replace('longdash', '8,3,')
  3569. .replace(/dot/g, '1,3,')
  3570. .replace('dash', '4,3,')
  3571. .replace(/,$/, '')
  3572. .split(','); // ending comma
  3573. i = value.length;
  3574. while (i--) {
  3575. value[i] = pInt(value[i]) * strokeWidth;
  3576. }
  3577. value = value.join(',')
  3578. .replace(/NaN/g, 'none'); // #3226
  3579. this.element.setAttribute('stroke-dasharray', value);
  3580. }
  3581. },
  3582. alignSetter: function(value) {
  3583. var convert = {
  3584. left: 'start',
  3585. center: 'middle',
  3586. right: 'end'
  3587. };
  3588. this.element.setAttribute('text-anchor', convert[value]);
  3589. },
  3590. opacitySetter: function(value, key, element) {
  3591. this[key] = value;
  3592. element.setAttribute(key, value);
  3593. },
  3594. titleSetter: function(value) {
  3595. var titleNode = this.element.getElementsByTagName('title')[0];
  3596. if (!titleNode) {
  3597. titleNode = doc.createElementNS(this.SVG_NS, 'title');
  3598. this.element.appendChild(titleNode);
  3599. }
  3600. // Remove text content if it exists
  3601. if (titleNode.firstChild) {
  3602. titleNode.removeChild(titleNode.firstChild);
  3603. }
  3604. titleNode.appendChild(
  3605. doc.createTextNode(
  3606. (String(pick(value), '')).replace(/<[^>]*>/g, '') // #3276, #3895
  3607. )
  3608. );
  3609. },
  3610. textSetter: function(value) {
  3611. if (value !== this.textStr) {
  3612. // Delete bBox memo when the text changes
  3613. delete this.bBox;
  3614. this.textStr = value;
  3615. if (this.added) {
  3616. this.renderer.buildText(this);
  3617. }
  3618. }
  3619. },
  3620. fillSetter: function(value, key, element) {
  3621. if (typeof value === 'string') {
  3622. element.setAttribute(key, value);
  3623. } else if (value) {
  3624. this.colorGradient(value, key, element);
  3625. }
  3626. },
  3627. visibilitySetter: function(value, key, element) {
  3628. // IE9-11 doesn't handle visibilty:inherit well, so we remove the attribute instead (#2881, #3909)
  3629. if (value === 'inherit') {
  3630. element.removeAttribute(key);
  3631. } else {
  3632. element.setAttribute(key, value);
  3633. }
  3634. },
  3635. zIndexSetter: function(value, key) {
  3636. var renderer = this.renderer,
  3637. parentGroup = this.parentGroup,
  3638. parentWrapper = parentGroup || renderer,
  3639. parentNode = parentWrapper.element || renderer.box,
  3640. childNodes,
  3641. otherElement,
  3642. otherZIndex,
  3643. element = this.element,
  3644. inserted,
  3645. run = this.added,
  3646. i;
  3647. if (defined(value)) {
  3648. element.zIndex = value; // So we can read it for other elements in the group
  3649. value = +value;
  3650. if (this[key] === value) { // Only update when needed (#3865)
  3651. run = false;
  3652. }
  3653. this[key] = value;
  3654. }
  3655. // Insert according to this and other elements' zIndex. Before .add() is called,
  3656. // nothing is done. Then on add, or by later calls to zIndexSetter, the node
  3657. // is placed on the right place in the DOM.
  3658. if (run) {
  3659. value = this.zIndex;
  3660. if (value && parentGroup) {
  3661. parentGroup.handleZ = true;
  3662. }
  3663. childNodes = parentNode.childNodes;
  3664. for (i = 0; i < childNodes.length && !inserted; i++) {
  3665. otherElement = childNodes[i];
  3666. otherZIndex = otherElement.zIndex;
  3667. if (otherElement !== element && (
  3668. // Insert before the first element with a higher zIndex
  3669. pInt(otherZIndex) > value ||
  3670. // If no zIndex given, insert before the first element with a zIndex
  3671. (!defined(value) && defined(otherZIndex)) ||
  3672. // Negative zIndex versus no zIndex:
  3673. // On all levels except the highest. If the parent is <svg>,
  3674. // then we don't want to put items before <desc> or <defs>
  3675. (value < 0 && !defined(otherZIndex) && parentNode !== renderer.box)
  3676. )) {
  3677. parentNode.insertBefore(element, otherElement);
  3678. inserted = true;
  3679. }
  3680. }
  3681. if (!inserted) {
  3682. parentNode.appendChild(element);
  3683. }
  3684. }
  3685. return inserted;
  3686. },
  3687. _defaultSetter: function(value, key, element) {
  3688. element.setAttribute(key, value);
  3689. }
  3690. });
  3691. // Some shared setters and getters
  3692. SVGElement.prototype.yGetter = SVGElement.prototype.xGetter;
  3693. SVGElement.prototype.translateXSetter = SVGElement.prototype.translateYSetter =
  3694. SVGElement.prototype.rotationSetter = SVGElement.prototype.verticalAlignSetter =
  3695. SVGElement.prototype.scaleXSetter = SVGElement.prototype.scaleYSetter = function(value, key) {
  3696. this[key] = value;
  3697. this.doTransform = true;
  3698. };
  3699. // WebKit and Batik have problems with a stroke-width of zero, so in this case we remove the
  3700. // stroke attribute altogether. #1270, #1369, #3065, #3072.
  3701. SVGElement.prototype['stroke-widthSetter'] = SVGElement.prototype.strokeSetter = function(value, key, element) {
  3702. this[key] = value;
  3703. // Only apply the stroke attribute if the stroke width is defined and larger than 0
  3704. if (this.stroke && this['stroke-width']) {
  3705. SVGElement.prototype.fillSetter.call(this, this.stroke, 'stroke', element); // use prototype as instance may be overridden
  3706. element.setAttribute('stroke-width', this['stroke-width']);
  3707. this.hasStroke = true;
  3708. } else if (key === 'stroke-width' && value === 0 && this.hasStroke) {
  3709. element.removeAttribute('stroke');
  3710. this.hasStroke = false;
  3711. }
  3712. };
  3713. /**
  3714. * Allows direct access to the Highcharts rendering layer in order to draw
  3715. * primitive shapes like circles, rectangles, paths or text directly on a chart,
  3716. * or independent from any chart. The SVGRenderer represents a wrapper object
  3717. * for SVGin modern browsers and through the VMLRenderer, for VML in IE < 8.
  3718. *
  3719. * An existing chart's renderer can be accessed through {@link Chart#renderer}.
  3720. * The renderer can also be used completely decoupled from a chart.
  3721. *
  3722. * @param {HTMLDOMElement} container - Where to put the SVG in the web page.
  3723. * @param {number} width - The width of the SVG.
  3724. * @param {number} height - The height of the SVG.
  3725. * @param {boolean} [forExport=false] - Whether the rendered content is intended
  3726. * for export.
  3727. * @param {boolean} [allowHTML=true] - Whether the renderer is allowed to
  3728. * include HTML text, which will be projected on top of the SVG.
  3729. *
  3730. * @example
  3731. * // Use directly without a chart object.
  3732. * var renderer = new Highcharts.Renderer(parentNode, 600, 400);
  3733. *
  3734. * @sample highcharts/members/renderer-on-chart - Annotating a chart programmatically.
  3735. * @sample highcharts/members/renderer-basic - Independedt SVG drawing.
  3736. *
  3737. * @class Highcharts.SVGRenderer
  3738. */
  3739. SVGRenderer = H.SVGRenderer = function() {
  3740. this.init.apply(this, arguments);
  3741. };
  3742. extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
  3743. /**
  3744. * A pointer to the renderer's associated Element class. The VMLRenderer
  3745. * will have a pointer to VMLElement here.
  3746. * @type {Highcharts.SVGElement}
  3747. */
  3748. Element: SVGElement,
  3749. SVG_NS: SVG_NS,
  3750. /**
  3751. * Initialize the SVGRenderer. Overridable initiator function that takes
  3752. * the same parameters as the constructor.
  3753. */
  3754. init: function(container, width, height, style, forExport, allowHTML) {
  3755. var renderer = this,
  3756. boxWrapper,
  3757. element,
  3758. desc;
  3759. boxWrapper = renderer.createElement('svg')
  3760. .attr({
  3761. 'version': '1.1',
  3762. 'class': 'highcharts-root'
  3763. })
  3764. .css(this.getStyle(style));
  3765. element = boxWrapper.element;
  3766. container.appendChild(element);
  3767. // For browsers other than IE, add the namespace attribute (#1978)
  3768. if (container.innerHTML.indexOf('xmlns') === -1) {
  3769. attr(element, 'xmlns', this.SVG_NS);
  3770. }
  3771. // object properties
  3772. renderer.isSVG = true;
  3773. /**
  3774. * The root `svg` node of the renderer.
  3775. * @type {SVGDOMElement}
  3776. */
  3777. this.box = element;
  3778. /**
  3779. * The wrapper for the root `svg` node of the renderer.
  3780. * @type {Highcharts.SVGElement}
  3781. */
  3782. this.boxWrapper = boxWrapper;
  3783. renderer.alignedObjects = [];
  3784. /**
  3785. * Page url used for internal references.
  3786. * @type {string}
  3787. */
  3788. // #24, #672, #1070
  3789. this.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ?
  3790. win.location.href
  3791. .replace(/#.*?$/, '') // remove the hash
  3792. .replace(/<[^>]*>/g, '') // wing cut HTML
  3793. .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes
  3794. .replace(/ /g, '%20') : // replace spaces (needed for Safari only)
  3795. '';
  3796. // Add description
  3797. desc = this.createElement('desc').add();
  3798. desc.element.appendChild(doc.createTextNode('Created with Highstock 5.0.12'));
  3799. renderer.defs = this.createElement('defs').add();
  3800. renderer.allowHTML = allowHTML;
  3801. renderer.forExport = forExport;
  3802. renderer.gradients = {}; // Object where gradient SvgElements are stored
  3803. renderer.cache = {}; // Cache for numerical bounding boxes
  3804. renderer.cacheKeys = [];
  3805. renderer.imgCount = 0;
  3806. renderer.setSize(width, height, false);
  3807. // Issue 110 workaround:
  3808. // In Firefox, if a div is positioned by percentage, its pixel position may land
  3809. // between pixels. The container itself doesn't display this, but an SVG element
  3810. // inside this container will be drawn at subpixel precision. In order to draw
  3811. // sharp lines, this must be compensated for. This doesn't seem to work inside
  3812. // iframes though (like in jsFiddle).
  3813. var subPixelFix, rect;
  3814. if (isFirefox && container.getBoundingClientRect) {
  3815. subPixelFix = function() {
  3816. css(container, {
  3817. left: 0,
  3818. top: 0
  3819. });
  3820. rect = container.getBoundingClientRect();
  3821. css(container, {
  3822. left: (Math.ceil(rect.left) - rect.left) + 'px',
  3823. top: (Math.ceil(rect.top) - rect.top) + 'px'
  3824. });
  3825. };
  3826. // run the fix now
  3827. subPixelFix();
  3828. // run it on resize
  3829. renderer.unSubPixelFix = addEvent(win, 'resize', subPixelFix);
  3830. }
  3831. },
  3832. /**
  3833. * Get the global style setting for the renderer.
  3834. * @private
  3835. * @param {CSSObject} style - Style settings.
  3836. * @return {CSSObject} The style settings mixed with defaults.
  3837. */
  3838. getStyle: function(style) {
  3839. this.style = extend({
  3840. fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif', // default font
  3841. fontSize: '12px'
  3842. }, style);
  3843. return this.style;
  3844. },
  3845. /**
  3846. * Apply the global style on the renderer, mixed with the default styles.
  3847. * @param {CSSObject} style - CSS to apply.
  3848. */
  3849. setStyle: function(style) {
  3850. this.boxWrapper.css(this.getStyle(style));
  3851. },
  3852. /**
  3853. * Detect whether the renderer is hidden. This happens when one of the
  3854. * parent elements has display: none. Used internally to detect when we need
  3855. * to render preliminarily in another div to get the text bounding boxes
  3856. * right.
  3857. *
  3858. * @returns {boolean} True if it is hidden.
  3859. */
  3860. isHidden: function() { // #608
  3861. return !this.boxWrapper.getBBox().width;
  3862. },
  3863. /**
  3864. * Destroys the renderer and its allocated members.
  3865. */
  3866. destroy: function() {
  3867. var renderer = this,
  3868. rendererDefs = renderer.defs;
  3869. renderer.box = null;
  3870. renderer.boxWrapper = renderer.boxWrapper.destroy();
  3871. // Call destroy on all gradient elements
  3872. destroyObjectProperties(renderer.gradients || {});
  3873. renderer.gradients = null;
  3874. // Defs are null in VMLRenderer
  3875. // Otherwise, destroy them here.
  3876. if (rendererDefs) {
  3877. renderer.defs = rendererDefs.destroy();
  3878. }
  3879. // Remove sub pixel fix handler (#982)
  3880. if (renderer.unSubPixelFix) {
  3881. renderer.unSubPixelFix();
  3882. }
  3883. renderer.alignedObjects = null;
  3884. return null;
  3885. },
  3886. /**
  3887. * Create a wrapper for an SVG element. Serves as a factory for
  3888. * {@link SVGElement}, but this function is itself mostly called from
  3889. * primitive factories like {@link SVGRenderer#path}, {@link
  3890. * SVGRenderer#rect} or {@link SVGRenderer#text}.
  3891. *
  3892. * @param {string} nodeName - The node name, for example `rect`, `g` etc.
  3893. * @returns {Highcharts.SVGElement} The generated SVGElement.
  3894. */
  3895. createElement: function(nodeName) {
  3896. var wrapper = new this.Element();
  3897. wrapper.init(this, nodeName);
  3898. return wrapper;
  3899. },
  3900. /**
  3901. * Dummy function for plugins, called every time the renderer is updated.
  3902. * Prior to Highcharts 5, this was used for the canvg renderer.
  3903. * @function
  3904. */
  3905. draw: noop,
  3906. /**
  3907. * Get converted radial gradient attributes according to the radial
  3908. * reference. Used internally from the {@link SVGElement#colorGradient}
  3909. * function.
  3910. *
  3911. * @private
  3912. */
  3913. getRadialAttr: function(radialReference, gradAttr) {
  3914. return {
  3915. cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
  3916. cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
  3917. r: gradAttr.r * radialReference[2]
  3918. };
  3919. },
  3920. getSpanWidth: function(wrapper, tspan) {
  3921. var renderer = this,
  3922. bBox = wrapper.getBBox(true),
  3923. actualWidth = bBox.width;
  3924. // Old IE cannot measure the actualWidth for SVG elements (#2314)
  3925. if (!svg && renderer.forExport) {
  3926. actualWidth = renderer.measureSpanWidth(tspan.firstChild.data, wrapper.styles);
  3927. }
  3928. return actualWidth;
  3929. },
  3930. applyEllipsis: function(wrapper, tspan, text, width) {
  3931. var renderer = this,
  3932. actualWidth = renderer.getSpanWidth(wrapper, tspan),
  3933. wasTooLong = actualWidth > width,
  3934. str = text,
  3935. currentIndex,
  3936. minIndex = 0,
  3937. maxIndex = text.length,
  3938. updateTSpan = function(s) {
  3939. tspan.removeChild(tspan.firstChild);
  3940. if (s) {
  3941. tspan.appendChild(doc.createTextNode(s));
  3942. }
  3943. };
  3944. if (wasTooLong) {
  3945. while (minIndex <= maxIndex) {
  3946. currentIndex = Math.ceil((minIndex + maxIndex) / 2);
  3947. str = text.substring(0, currentIndex) + '\u2026';
  3948. updateTSpan(str);
  3949. actualWidth = renderer.getSpanWidth(wrapper, tspan);
  3950. if (minIndex === maxIndex) {
  3951. // Complete
  3952. minIndex = maxIndex + 1;
  3953. } else if (actualWidth > width) {
  3954. // Too large. Set max index to current.
  3955. maxIndex = currentIndex - 1;
  3956. } else {
  3957. // Within width. Set min index to current.
  3958. minIndex = currentIndex;
  3959. }
  3960. }
  3961. // If max index was 0 it means just ellipsis was also to large.
  3962. if (maxIndex === 0) {
  3963. // Remove ellipses.
  3964. updateTSpan('');
  3965. }
  3966. }
  3967. return wasTooLong;
  3968. },
  3969. /**
  3970. * Parse a simple HTML string into SVG tspans. Called internally when text
  3971. * is set on an SVGElement. The function supports a subset of HTML tags,
  3972. * CSS text features like `width`, `text-overflow`, `white-space`, and
  3973. * also attributes like `href` and `style`.
  3974. * @private
  3975. * @param {Highcharts.SVGElement} wrapper The parent SVGElement.
  3976. */
  3977. buildText: function(wrapper) {
  3978. var textNode = wrapper.element,
  3979. renderer = this,
  3980. forExport = renderer.forExport,
  3981. textStr = pick(wrapper.textStr, '').toString(),
  3982. hasMarkup = textStr.indexOf('<') !== -1,
  3983. lines,
  3984. childNodes = textNode.childNodes,
  3985. clsRegex,
  3986. styleRegex,
  3987. hrefRegex,
  3988. wasTooLong,
  3989. parentX = attr(textNode, 'x'),
  3990. textStyles = wrapper.styles,
  3991. width = wrapper.textWidth,
  3992. textLineHeight = textStyles && textStyles.lineHeight,
  3993. textOutline = textStyles && textStyles.textOutline,
  3994. ellipsis = textStyles && textStyles.textOverflow === 'ellipsis',
  3995. noWrap = textStyles && textStyles.whiteSpace === 'nowrap',
  3996. fontSize = textStyles && textStyles.fontSize,
  3997. textCache,
  3998. isSubsequentLine,
  3999. i = childNodes.length,
  4000. tempParent = width && !wrapper.added && this.box,
  4001. getLineHeight = function(tspan) {
  4002. var fontSizeStyle;
  4003. fontSizeStyle = /(px|em)$/.test(tspan && tspan.style.fontSize) ?
  4004. tspan.style.fontSize :
  4005. (fontSize || renderer.style.fontSize || 12);
  4006. return textLineHeight ?
  4007. pInt(textLineHeight) :
  4008. renderer.fontMetrics(
  4009. fontSizeStyle,
  4010. // Get the computed size from parent if not explicit
  4011. tspan.getAttribute('style') ? tspan : textNode
  4012. ).h;
  4013. },
  4014. unescapeAngleBrackets = function(inputStr) {
  4015. return inputStr.replace(/&lt;/g, '<').replace(/&gt;/g, '>');
  4016. };
  4017. // The buildText code is quite heavy, so if we're not changing something
  4018. // that affects the text, skip it (#6113).
  4019. textCache = [
  4020. textStr,
  4021. ellipsis,
  4022. noWrap,
  4023. textLineHeight,
  4024. textOutline,
  4025. fontSize,
  4026. width
  4027. ].join(',');
  4028. if (textCache === wrapper.textCache) {
  4029. return;
  4030. }
  4031. wrapper.textCache = textCache;
  4032. /// remove old text
  4033. while (i--) {
  4034. textNode.removeChild(childNodes[i]);
  4035. }
  4036. // Skip tspans, add text directly to text node. The forceTSpan is a hook
  4037. // used in text outline hack.
  4038. if (!hasMarkup && !textOutline && !ellipsis && !width && textStr.indexOf(' ') === -1) {
  4039. textNode.appendChild(doc.createTextNode(unescapeAngleBrackets(textStr)));
  4040. // Complex strings, add more logic
  4041. } else {
  4042. clsRegex = /<.*class="([^"]+)".*>/;
  4043. styleRegex = /<.*style="([^"]+)".*>/;
  4044. hrefRegex = /<.*href="([^"]+)".*>/;
  4045. if (tempParent) {
  4046. tempParent.appendChild(textNode); // attach it to the DOM to read offset width
  4047. }
  4048. if (hasMarkup) {
  4049. lines = textStr
  4050. .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
  4051. .replace(/<(i|em)>/g, '<span style="font-style:italic">')
  4052. .replace(/<a/g, '<span')
  4053. .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
  4054. .split(/<br.*?>/g);
  4055. } else {
  4056. lines = [textStr];
  4057. }
  4058. // Trim empty lines (#5261)
  4059. lines = grep(lines, function(line) {
  4060. return line !== '';
  4061. });
  4062. // build the lines
  4063. each(lines, function buildTextLines(line, lineNo) {
  4064. var spans,
  4065. spanNo = 0;
  4066. line = line
  4067. .replace(/^\s+|\s+$/g, '') // Trim to prevent useless/costly process on the spaces (#5258)
  4068. .replace(/<span/g, '|||<span')
  4069. .replace(/<\/span>/g, '</span>|||');
  4070. spans = line.split('|||');
  4071. each(spans, function buildTextSpans(span) {
  4072. if (span !== '' || spans.length === 1) {
  4073. var attributes = {},
  4074. tspan = doc.createElementNS(renderer.SVG_NS, 'tspan'),
  4075. spanCls,
  4076. spanStyle; // #390
  4077. if (clsRegex.test(span)) {
  4078. spanCls = span.match(clsRegex)[1];
  4079. attr(tspan, 'class', spanCls);
  4080. }
  4081. if (styleRegex.test(span)) {
  4082. spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');
  4083. attr(tspan, 'style', spanStyle);
  4084. }
  4085. if (hrefRegex.test(span) && !forExport) { // Not for export - #1529
  4086. attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
  4087. css(tspan, {
  4088. cursor: 'pointer'
  4089. });
  4090. }
  4091. span = unescapeAngleBrackets(span.replace(/<(.|\n)*?>/g, '') || ' ');
  4092. // Nested tags aren't supported, and cause crash in Safari (#1596)
  4093. if (span !== ' ') {
  4094. // add the text node
  4095. tspan.appendChild(doc.createTextNode(span));
  4096. if (!spanNo) { // first span in a line, align it to the left
  4097. if (lineNo && parentX !== null) {
  4098. attributes.x = parentX;
  4099. }
  4100. } else {
  4101. attributes.dx = 0; // #16
  4102. }
  4103. // add attributes
  4104. attr(tspan, attributes);
  4105. // Append it
  4106. textNode.appendChild(tspan);
  4107. // first span on subsequent line, add the line height
  4108. if (!spanNo && isSubsequentLine) {
  4109. // allow getting the right offset height in exporting in IE
  4110. if (!svg && forExport) {
  4111. css(tspan, {
  4112. display: 'block'
  4113. });
  4114. }
  4115. // Set the line height based on the font size of either
  4116. // the text element or the tspan element
  4117. attr(
  4118. tspan,
  4119. 'dy',
  4120. getLineHeight(tspan)
  4121. );
  4122. }
  4123. /*if (width) {
  4124. renderer.breakText(wrapper, width);
  4125. }*/
  4126. // Check width and apply soft breaks or ellipsis
  4127. if (width) {
  4128. var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
  4129. hasWhiteSpace = spans.length > 1 || lineNo || (words.length > 1 && !noWrap),
  4130. tooLong,
  4131. rest = [],
  4132. actualWidth,
  4133. dy = getLineHeight(tspan),
  4134. rotation = wrapper.rotation;
  4135. if (ellipsis) {
  4136. wasTooLong = renderer.applyEllipsis(wrapper, tspan, span, width);
  4137. }
  4138. while (!ellipsis && hasWhiteSpace && (words.length || rest.length)) {
  4139. wrapper.rotation = 0; // discard rotation when computing box
  4140. actualWidth = renderer.getSpanWidth(wrapper, tspan);
  4141. tooLong = actualWidth > width;
  4142. // For ellipsis, do a binary search for the correct string length
  4143. if (wasTooLong === undefined) {
  4144. wasTooLong = tooLong; // First time
  4145. }
  4146. // Looping down, this is the first word sequence that is not too long,
  4147. // so we can move on to build the next line.
  4148. if (!tooLong || words.length === 1) {
  4149. words = rest;
  4150. rest = [];
  4151. if (words.length && !noWrap) {
  4152. tspan = doc.createElementNS(SVG_NS, 'tspan');
  4153. attr(tspan, {
  4154. dy: dy,
  4155. x: parentX
  4156. });
  4157. if (spanStyle) { // #390
  4158. attr(tspan, 'style', spanStyle);
  4159. }
  4160. textNode.appendChild(tspan);
  4161. }
  4162. if (actualWidth > width) { // a single word is pressing it out
  4163. width = actualWidth;
  4164. }
  4165. } else { // append to existing line tspan
  4166. tspan.removeChild(tspan.firstChild);
  4167. rest.unshift(words.pop());
  4168. }
  4169. if (words.length) {
  4170. tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
  4171. }
  4172. }
  4173. wrapper.rotation = rotation;
  4174. }
  4175. spanNo++;
  4176. }
  4177. }
  4178. });
  4179. // To avoid beginning lines that doesn't add to the textNode (#6144)
  4180. isSubsequentLine = isSubsequentLine || textNode.childNodes.length;
  4181. });
  4182. if (wasTooLong) {
  4183. wrapper.attr('title', wrapper.textStr);
  4184. }
  4185. if (tempParent) {
  4186. tempParent.removeChild(textNode); // attach it to the DOM to read offset width
  4187. }
  4188. // Apply the text outline
  4189. if (textOutline && wrapper.applyTextOutline) {
  4190. wrapper.applyTextOutline(textOutline);
  4191. }
  4192. }
  4193. },
  4194. /*
  4195. breakText: function (wrapper, width) {
  4196. var bBox = wrapper.getBBox(),
  4197. node = wrapper.element,
  4198. textLength = node.textContent.length,
  4199. pos = Math.round(width * textLength / bBox.width), // try this position first, based on average character width
  4200. increment = 0,
  4201. finalPos;
  4202. if (bBox.width > width) {
  4203. while (finalPos === undefined) {
  4204. textLength = node.getSubStringLength(0, pos);
  4205. if (textLength <= width) {
  4206. if (increment === -1) {
  4207. finalPos = pos;
  4208. } else {
  4209. increment = 1;
  4210. }
  4211. } else {
  4212. if (increment === 1) {
  4213. finalPos = pos - 1;
  4214. } else {
  4215. increment = -1;
  4216. }
  4217. }
  4218. pos += increment;
  4219. }
  4220. }
  4221. console.log('width', width, 'stringWidth', node.getSubStringLength(0, finalPos))
  4222. },
  4223. */
  4224. /**
  4225. * Returns white for dark colors and black for bright colors.
  4226. *
  4227. * @param {ColorString} rgba - The color to get the contrast for.
  4228. * @returns {string} The contrast color, either `#000000` or `#FFFFFF`.
  4229. */
  4230. getContrast: function(rgba) {
  4231. rgba = color(rgba).rgba;
  4232. // The threshold may be discussed. Here's a proposal for adding
  4233. // different weight to the color channels (#6216)
  4234. /*
  4235. rgba[0] *= 1; // red
  4236. rgba[1] *= 1.2; // green
  4237. rgba[2] *= 0.7; // blue
  4238. */
  4239. return rgba[0] + rgba[1] + rgba[2] > 2 * 255 ? '#000000' : '#FFFFFF';
  4240. },
  4241. /**
  4242. * Create a button with preset states.
  4243. * @param {string} text - The text or HTML to draw.
  4244. * @param {number} x - The x position of the button's left side.
  4245. * @param {number} y - The y position of the button's top side.
  4246. * @param {Function} callback - The function to execute on button click or
  4247. * touch.
  4248. * @param {SVGAttributes} [normalState] - SVG attributes for the normal
  4249. * state.
  4250. * @param {SVGAttributes} [hoverState] - SVG attributes for the hover state.
  4251. * @param {SVGAttributes} [pressedState] - SVG attributes for the pressed
  4252. * state.
  4253. * @param {SVGAttributes} [disabledState] - SVG attributes for the disabled
  4254. * state.
  4255. * @param {Symbol} [shape=rect] - The shape type.
  4256. * @returns {SVGRenderer} The button element.
  4257. */
  4258. button: function(text, x, y, callback, normalState, hoverState, pressedState, disabledState, shape) {
  4259. var label = this.label(text, x, y, shape, null, null, null, null, 'button'),
  4260. curState = 0;
  4261. // Default, non-stylable attributes
  4262. label.attr(merge({
  4263. 'padding': 8,
  4264. 'r': 2
  4265. }, normalState));
  4266. // Presentational
  4267. var normalStyle,
  4268. hoverStyle,
  4269. pressedStyle,
  4270. disabledStyle;
  4271. // Normal state - prepare the attributes
  4272. normalState = merge({
  4273. fill: '#f7f7f7',
  4274. stroke: '#cccccc',
  4275. 'stroke-width': 1,
  4276. style: {
  4277. color: '#333333',
  4278. cursor: 'pointer',
  4279. fontWeight: 'normal'
  4280. }
  4281. }, normalState);
  4282. normalStyle = normalState.style;
  4283. delete normalState.style;
  4284. // Hover state
  4285. hoverState = merge(normalState, {
  4286. fill: '#e6e6e6'
  4287. }, hoverState);
  4288. hoverStyle = hoverState.style;
  4289. delete hoverState.style;
  4290. // Pressed state
  4291. pressedState = merge(normalState, {
  4292. fill: '#e6ebf5',
  4293. style: {
  4294. color: '#000000',
  4295. fontWeight: 'bold'
  4296. }
  4297. }, pressedState);
  4298. pressedStyle = pressedState.style;
  4299. delete pressedState.style;
  4300. // Disabled state
  4301. disabledState = merge(normalState, {
  4302. style: {
  4303. color: '#cccccc'
  4304. }
  4305. }, disabledState);
  4306. disabledStyle = disabledState.style;
  4307. delete disabledState.style;
  4308. // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667).
  4309. addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function() {
  4310. if (curState !== 3) {
  4311. label.setState(1);
  4312. }
  4313. });
  4314. addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function() {
  4315. if (curState !== 3) {
  4316. label.setState(curState);
  4317. }
  4318. });
  4319. label.setState = function(state) {
  4320. // Hover state is temporary, don't record it
  4321. if (state !== 1) {
  4322. label.state = curState = state;
  4323. }
  4324. // Update visuals
  4325. label.removeClass(/highcharts-button-(normal|hover|pressed|disabled)/)
  4326. .addClass('highcharts-button-' + ['normal', 'hover', 'pressed', 'disabled'][state || 0]);
  4327. label.attr([normalState, hoverState, pressedState, disabledState][state || 0])
  4328. .css([normalStyle, hoverStyle, pressedStyle, disabledStyle][state || 0]);
  4329. };
  4330. // Presentational attributes
  4331. label
  4332. .attr(normalState)
  4333. .css(extend({
  4334. cursor: 'default'
  4335. }, normalStyle));
  4336. return label
  4337. .on('click', function(e) {
  4338. if (curState !== 3) {
  4339. callback.call(label, e);
  4340. }
  4341. });
  4342. },
  4343. /**
  4344. * Make a straight line crisper by not spilling out to neighbour pixels.
  4345. *
  4346. * @param {Array} points - The original points on the format `['M', 0, 0,
  4347. * 'L', 100, 0]`.
  4348. * @param {number} width - The width of the line.
  4349. * @returns {Array} The original points array, but modified to render
  4350. * crisply.
  4351. */
  4352. crispLine: function(points, width) {
  4353. // normalize to a crisp line
  4354. if (points[1] === points[4]) {
  4355. // Substract due to #1129. Now bottom and left axis gridlines behave the same.
  4356. points[1] = points[4] = Math.round(points[1]) - (width % 2 / 2);
  4357. }
  4358. if (points[2] === points[5]) {
  4359. points[2] = points[5] = Math.round(points[2]) + (width % 2 / 2);
  4360. }
  4361. return points;
  4362. },
  4363. /**
  4364. * Draw a path, wraps the SVG `path` element.
  4365. *
  4366. * @param {Array} [path] An SVG path definition in array form.
  4367. *
  4368. * @example
  4369. * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z'])
  4370. * .attr({ stroke: '#ff00ff' })
  4371. * .add();
  4372. * @returns {Highcharts.SVGElement} The generated wrapper element.
  4373. *
  4374. * @sample highcharts/members/renderer-path-on-chart/
  4375. * Draw a path in a chart
  4376. * @sample highcharts/members/renderer-path/
  4377. * Draw a path independent from a chart
  4378. *
  4379. */
  4380. /**
  4381. * Draw a path, wraps the SVG `path` element.
  4382. *
  4383. * @param {SVGAttributes} [attribs] The initial attributes.
  4384. * @returns {Highcharts.SVGElement} The generated wrapper element.
  4385. */
  4386. path: function(path) {
  4387. var attribs = {
  4388. fill: 'none'
  4389. };
  4390. if (isArray(path)) {
  4391. attribs.d = path;
  4392. } else if (isObject(path)) { // attributes
  4393. extend(attribs, path);
  4394. }
  4395. return this.createElement('path').attr(attribs);
  4396. },
  4397. /**
  4398. * Draw a circle, wraps the SVG `circle` element.
  4399. *
  4400. * @param {number} [x] The center x position.
  4401. * @param {number} [y] The center y position.
  4402. * @param {number} [r] The radius.
  4403. * @returns {Highcharts.SVGElement} The generated wrapper element.
  4404. *
  4405. * @sample highcharts/members/renderer-circle/ Drawing a circle
  4406. */
  4407. /**
  4408. * Draw a circle, wraps the SVG `circle` element.
  4409. *
  4410. * @param {SVGAttributes} [attribs] The initial attributes.
  4411. * @returns {Highcharts.SVGElement} The generated wrapper element.
  4412. */
  4413. circle: function(x, y, r) {
  4414. var attribs = isObject(x) ? x : {
  4415. x: x,
  4416. y: y,
  4417. r: r
  4418. },
  4419. wrapper = this.createElement('circle');
  4420. // Setting x or y translates to cx and cy
  4421. wrapper.xSetter = wrapper.ySetter = function(value, key, element) {
  4422. element.setAttribute('c' + key, value);
  4423. };
  4424. return wrapper.attr(attribs);
  4425. },
  4426. /**
  4427. * Draw and return an arc.
  4428. * @param {number} [x=0] Center X position.
  4429. * @param {number} [y=0] Center Y position.
  4430. * @param {number} [r=0] The outer radius of the arc.
  4431. * @param {number} [innerR=0] Inner radius like used in donut charts.
  4432. * @param {number} [start=0] The starting angle of the arc in radians, where
  4433. * 0 is to the right and `-Math.PI/2` is up.
  4434. * @param {number} [end=0] The ending angle of the arc in radians, where 0
  4435. * is to the right and `-Math.PI/2` is up.
  4436. * @returns {Highcharts.SVGElement} The generated wrapper element.
  4437. *
  4438. * @sample highcharts/members/renderer-arc/
  4439. * Drawing an arc
  4440. */
  4441. /**
  4442. * Draw and return an arc. Overloaded function that takes arguments object.
  4443. * @param {SVGAttributes} attribs Initial SVG attributes.
  4444. * @returns {Highcharts.SVGElement} The generated wrapper element.
  4445. */
  4446. arc: function(x, y, r, innerR, start, end) {
  4447. var arc,
  4448. options;
  4449. if (isObject(x)) {
  4450. options = x;
  4451. y = options.y;
  4452. r = options.r;
  4453. innerR = options.innerR;
  4454. start = options.start;
  4455. end = options.end;
  4456. x = options.x;
  4457. } else {
  4458. options = {
  4459. innerR: innerR,
  4460. start: start,
  4461. end: end
  4462. };
  4463. }
  4464. // Arcs are defined as symbols for the ability to set
  4465. // attributes in attr and animate
  4466. arc = this.symbol('arc', x, y, r, r, options);
  4467. arc.r = r; // #959
  4468. return arc;
  4469. },
  4470. /**
  4471. * Draw and return a rectangle.
  4472. * @param {number} [x] Left position.
  4473. * @param {number} [y] Top position.
  4474. * @param {number} [width] Width of the rectangle.
  4475. * @param {number} [height] Height of the rectangle.
  4476. * @param {number} [r] Border corner radius.
  4477. * @param {number} [strokeWidth] A stroke width can be supplied to allow
  4478. * crisp drawing.
  4479. * @returns {Highcharts.SVGElement} The generated wrapper element.
  4480. */
  4481. /**
  4482. * Draw and return a rectangle.
  4483. * @param {SVGAttributes} [attributes]
  4484. * General SVG attributes for the rectangle.
  4485. * @return {Highcharts.SVGElement}
  4486. * The generated wrapper element.
  4487. *
  4488. * @sample highcharts/members/renderer-rect-on-chart/
  4489. * Draw a rectangle in a chart
  4490. * @sample highcharts/members/renderer-rect/
  4491. * Draw a rectangle independent from a chart
  4492. */
  4493. rect: function(x, y, width, height, r, strokeWidth) {
  4494. r = isObject(x) ? x.r : r;
  4495. var wrapper = this.createElement('rect'),
  4496. attribs = isObject(x) ? x : x === undefined ? {} : {
  4497. x: x,
  4498. y: y,
  4499. width: Math.max(width, 0),
  4500. height: Math.max(height, 0)
  4501. };
  4502. if (strokeWidth !== undefined) {
  4503. attribs.strokeWidth = strokeWidth;
  4504. attribs = wrapper.crisp(attribs);
  4505. }
  4506. attribs.fill = 'none';
  4507. if (r) {
  4508. attribs.r = r;
  4509. }
  4510. wrapper.rSetter = function(value, key, element) {
  4511. attr(element, {
  4512. rx: value,
  4513. ry: value
  4514. });
  4515. };
  4516. return wrapper.attr(attribs);
  4517. },
  4518. /**
  4519. * Resize the {@link SVGRenderer#box} and re-align all aligned child
  4520. * elements.
  4521. * @param {number} width The new pixel width.
  4522. * @param {number} height The new pixel height.
  4523. * @param {boolean} animate Whether to animate.
  4524. */
  4525. setSize: function(width, height, animate) {
  4526. var renderer = this,
  4527. alignedObjects = renderer.alignedObjects,
  4528. i = alignedObjects.length;
  4529. renderer.width = width;
  4530. renderer.height = height;
  4531. renderer.boxWrapper.animate({
  4532. width: width,
  4533. height: height
  4534. }, {
  4535. step: function() {
  4536. this.attr({
  4537. viewBox: '0 0 ' + this.attr('width') + ' ' + this.attr('height')
  4538. });
  4539. },
  4540. duration: pick(animate, true) ? undefined : 0
  4541. });
  4542. while (i--) {
  4543. alignedObjects[i].align();
  4544. }
  4545. },
  4546. /**
  4547. * Create and return an svg group element. Child {@link Highcharts.SVGElement}
  4548. * objects are added to the group by using the group as the first parameter
  4549. * in {@link Highcharts.SVGElement#add|add()}.
  4550. *
  4551. * @param {string} [name] The group will be given a class name of
  4552. * `highcharts-{name}`. This can be used for styling and scripting.
  4553. * @returns {Highcharts.SVGElement} The generated wrapper element.
  4554. *
  4555. * @sample highcharts/members/renderer-g/
  4556. * Show and hide grouped objects
  4557. */
  4558. g: function(name) {
  4559. var elem = this.createElement('g');
  4560. return name ? elem.attr({
  4561. 'class': 'highcharts-' + name
  4562. }) : elem;
  4563. },
  4564. /**
  4565. * Display an image.
  4566. * @param {string} src The image source.
  4567. * @param {number} [x] The X position.
  4568. * @param {number} [y] The Y position.
  4569. * @param {number} [width] The image width. If omitted, it defaults to the
  4570. * image file width.
  4571. * @param {number} [height] The image height. If omitted it defaults to the
  4572. * image file height.
  4573. * @returns {Highcharts.SVGElement} The generated wrapper element.
  4574. *
  4575. * @sample highcharts/members/renderer-image-on-chart/
  4576. * Add an image in a chart
  4577. * @sample highcharts/members/renderer-image/
  4578. * Add an image independent of a chart
  4579. */
  4580. image: function(src, x, y, width, height) {
  4581. var attribs = {
  4582. preserveAspectRatio: 'none'
  4583. },
  4584. elemWrapper;
  4585. // optional properties
  4586. if (arguments.length > 1) {
  4587. extend(attribs, {
  4588. x: x,
  4589. y: y,
  4590. width: width,
  4591. height: height
  4592. });
  4593. }
  4594. elemWrapper = this.createElement('image').attr(attribs);
  4595. // set the href in the xlink namespace
  4596. if (elemWrapper.element.setAttributeNS) {
  4597. elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
  4598. 'href', src);
  4599. } else {
  4600. // could be exporting in IE
  4601. // using href throws "not supported" in ie7 and under, requries regex shim to fix later
  4602. elemWrapper.element.setAttribute('hc-svg-href', src);
  4603. }
  4604. return elemWrapper;
  4605. },
  4606. /**
  4607. * Draw a symbol out of pre-defined shape paths from {@SVGRenderer#symbols}.
  4608. * It is used in Highcharts for point makers, which cake a `symbol` option,
  4609. * and label and button backgrounds like in the tooltip and stock flags.
  4610. *
  4611. * @param {Symbol} symbol - The symbol name.
  4612. * @param {number} x - The X coordinate for the top left position.
  4613. * @param {number} y - The Y coordinate for the top left position.
  4614. * @param {number} width - The pixel width.
  4615. * @param {number} height - The pixel height.
  4616. * @param {Object} [options] - Additional options, depending on the actual
  4617. * symbol drawn.
  4618. * @param {number} [options.anchorX] - The anchor X position for the
  4619. * `callout` symbol. This is where the chevron points to.
  4620. * @param {number} [options.anchorY] - The anchor Y position for the
  4621. * `callout` symbol. This is where the chevron points to.
  4622. * @param {number} [options.end] - The end angle of an `arc` symbol.
  4623. * @param {boolean} [options.open] - Whether to draw `arc` symbol open or
  4624. * closed.
  4625. * @param {number} [options.r] - The radius of an `arc` symbol, or the
  4626. * border radius for the `callout` symbol.
  4627. * @param {number} [options.start] - The start angle of an `arc` symbol.
  4628. */
  4629. symbol: function(symbol, x, y, width, height, options) {
  4630. var ren = this,
  4631. obj,
  4632. imageRegex = /^url\((.*?)\)$/,
  4633. isImage = imageRegex.test(symbol),
  4634. sym = !isImage && (this.symbols[symbol] ? symbol : 'circle'),
  4635. // get the symbol definition function
  4636. symbolFn = sym && this.symbols[sym],
  4637. // check if there's a path defined for this symbol
  4638. path = defined(x) && symbolFn && symbolFn.call(
  4639. this.symbols,
  4640. Math.round(x),
  4641. Math.round(y),
  4642. width,
  4643. height,
  4644. options
  4645. ),
  4646. imageSrc,
  4647. centerImage;
  4648. if (symbolFn) {
  4649. obj = this.path(path);
  4650. obj.attr('fill', 'none');
  4651. // expando properties for use in animate and attr
  4652. extend(obj, {
  4653. symbolName: sym,
  4654. x: x,
  4655. y: y,
  4656. width: width,
  4657. height: height
  4658. });
  4659. if (options) {
  4660. extend(obj, options);
  4661. }
  4662. // Image symbols
  4663. } else if (isImage) {
  4664. imageSrc = symbol.match(imageRegex)[1];
  4665. // Create the image synchronously, add attribs async
  4666. obj = this.image(imageSrc);
  4667. // The image width is not always the same as the symbol width. The
  4668. // image may be centered within the symbol, as is the case when
  4669. // image shapes are used as label backgrounds, for example in flags.
  4670. obj.imgwidth = pick(
  4671. symbolSizes[imageSrc] && symbolSizes[imageSrc].width,
  4672. options && options.width
  4673. );
  4674. obj.imgheight = pick(
  4675. symbolSizes[imageSrc] && symbolSizes[imageSrc].height,
  4676. options && options.height
  4677. );
  4678. /**
  4679. * Set the size and position
  4680. */
  4681. centerImage = function() {
  4682. obj.attr({
  4683. width: obj.width,
  4684. height: obj.height
  4685. });
  4686. };
  4687. /**
  4688. * Width and height setters that take both the image's physical size
  4689. * and the label size into consideration, and translates the image
  4690. * to center within the label.
  4691. */
  4692. each(['width', 'height'], function(key) {
  4693. obj[key + 'Setter'] = function(value, key) {
  4694. var attribs = {},
  4695. imgSize = this['img' + key],
  4696. trans = key === 'width' ? 'translateX' : 'translateY';
  4697. this[key] = value;
  4698. if (defined(imgSize)) {
  4699. if (this.element) {
  4700. this.element.setAttribute(key, imgSize);
  4701. }
  4702. if (!this.alignByTranslate) {
  4703. attribs[trans] = ((this[key] || 0) - imgSize) / 2;
  4704. this.attr(attribs);
  4705. }
  4706. }
  4707. };
  4708. });
  4709. if (defined(x)) {
  4710. obj.attr({
  4711. x: x,
  4712. y: y
  4713. });
  4714. }
  4715. obj.isImg = true;
  4716. if (defined(obj.imgwidth) && defined(obj.imgheight)) {
  4717. centerImage();
  4718. } else {
  4719. // Initialize image to be 0 size so export will still function if there's no cached sizes.
  4720. obj.attr({
  4721. width: 0,
  4722. height: 0
  4723. });
  4724. // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8,
  4725. // the created element must be assigned to a variable in order to load (#292).
  4726. createElement('img', {
  4727. onload: function() {
  4728. var chart = charts[ren.chartIndex];
  4729. // Special case for SVGs on IE11, the width is not accessible until the image is
  4730. // part of the DOM (#2854).
  4731. if (this.width === 0) {
  4732. css(this, {
  4733. position: 'absolute',
  4734. top: '-999em'
  4735. });
  4736. doc.body.appendChild(this);
  4737. }
  4738. // Center the image
  4739. symbolSizes[imageSrc] = { // Cache for next
  4740. width: this.width,
  4741. height: this.height
  4742. };
  4743. obj.imgwidth = this.width;
  4744. obj.imgheight = this.height;
  4745. if (obj.element) {
  4746. centerImage();
  4747. }
  4748. // Clean up after #2854 workaround.
  4749. if (this.parentNode) {
  4750. this.parentNode.removeChild(this);
  4751. }
  4752. // Fire the load event when all external images are loaded
  4753. ren.imgCount--;
  4754. if (!ren.imgCount && chart && chart.onload) {
  4755. chart.onload();
  4756. }
  4757. },
  4758. src: imageSrc
  4759. });
  4760. this.imgCount++;
  4761. }
  4762. }
  4763. return obj;
  4764. },
  4765. /**
  4766. * @typedef {string} Symbol
  4767. *
  4768. * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`,
  4769. * `triangle`, `triangle-down`. Symbols are used internally for point
  4770. * markers, button and label borders and backgrounds, or custom shapes.
  4771. * Extendable by adding to {@link SVGRenderer#symbols}.
  4772. */
  4773. /**
  4774. * An extendable collection of functions for defining symbol paths.
  4775. */
  4776. symbols: {
  4777. 'circle': function(x, y, w, h) {
  4778. // Return a full arc
  4779. return this.arc(x + w / 2, y + h / 2, w / 2, h / 2, {
  4780. start: 0,
  4781. end: Math.PI * 2,
  4782. open: false
  4783. });
  4784. },
  4785. 'square': function(x, y, w, h) {
  4786. return [
  4787. 'M', x, y,
  4788. 'L', x + w, y,
  4789. x + w, y + h,
  4790. x, y + h,
  4791. 'Z'
  4792. ];
  4793. },
  4794. 'triangle': function(x, y, w, h) {
  4795. return [
  4796. 'M', x + w / 2, y,
  4797. 'L', x + w, y + h,
  4798. x, y + h,
  4799. 'Z'
  4800. ];
  4801. },
  4802. 'triangle-down': function(x, y, w, h) {
  4803. return [
  4804. 'M', x, y,
  4805. 'L', x + w, y,
  4806. x + w / 2, y + h,
  4807. 'Z'
  4808. ];
  4809. },
  4810. 'diamond': function(x, y, w, h) {
  4811. return [
  4812. 'M', x + w / 2, y,
  4813. 'L', x + w, y + h / 2,
  4814. x + w / 2, y + h,
  4815. x, y + h / 2,
  4816. 'Z'
  4817. ];
  4818. },
  4819. 'arc': function(x, y, w, h, options) {
  4820. var start = options.start,
  4821. rx = options.r || w,
  4822. ry = options.r || h || w,
  4823. end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561)
  4824. innerRadius = options.innerR,
  4825. open = options.open,
  4826. cosStart = Math.cos(start),
  4827. sinStart = Math.sin(start),
  4828. cosEnd = Math.cos(end),
  4829. sinEnd = Math.sin(end),
  4830. longArc = options.end - start < Math.PI ? 0 : 1,
  4831. arc;
  4832. arc = [
  4833. 'M',
  4834. x + rx * cosStart,
  4835. y + ry * sinStart,
  4836. 'A', // arcTo
  4837. rx, // x radius
  4838. ry, // y radius
  4839. 0, // slanting
  4840. longArc, // long or short arc
  4841. 1, // clockwise
  4842. x + rx * cosEnd,
  4843. y + ry * sinEnd
  4844. ];
  4845. if (defined(innerRadius)) {
  4846. arc.push(
  4847. open ? 'M' : 'L',
  4848. x + innerRadius * cosEnd,
  4849. y + innerRadius * sinEnd,
  4850. 'A', // arcTo
  4851. innerRadius, // x radius
  4852. innerRadius, // y radius
  4853. 0, // slanting
  4854. longArc, // long or short arc
  4855. 0, // clockwise
  4856. x + innerRadius * cosStart,
  4857. y + innerRadius * sinStart
  4858. );
  4859. }
  4860. arc.push(open ? '' : 'Z'); // close
  4861. return arc;
  4862. },
  4863. /**
  4864. * Callout shape used for default tooltips, also used for rounded rectangles in VML
  4865. */
  4866. callout: function(x, y, w, h, options) {
  4867. var arrowLength = 6,
  4868. halfDistance = 6,
  4869. r = Math.min((options && options.r) || 0, w, h),
  4870. safeDistance = r + halfDistance,
  4871. anchorX = options && options.anchorX,
  4872. anchorY = options && options.anchorY,
  4873. path;
  4874. path = [
  4875. 'M', x + r, y,
  4876. 'L', x + w - r, y, // top side
  4877. 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
  4878. 'L', x + w, y + h - r, // right side
  4879. 'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-right corner
  4880. 'L', x + r, y + h, // bottom side
  4881. 'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
  4882. 'L', x, y + r, // left side
  4883. 'C', x, y, x, y, x + r, y // top-left corner
  4884. ];
  4885. // Anchor on right side
  4886. if (anchorX && anchorX > w) {
  4887. // Chevron
  4888. if (anchorY > y + safeDistance && anchorY < y + h - safeDistance) {
  4889. path.splice(13, 3,
  4890. 'L', x + w, anchorY - halfDistance,
  4891. x + w + arrowLength, anchorY,
  4892. x + w, anchorY + halfDistance,
  4893. x + w, y + h - r
  4894. );
  4895. // Simple connector
  4896. } else {
  4897. path.splice(13, 3,
  4898. 'L', x + w, h / 2,
  4899. anchorX, anchorY,
  4900. x + w, h / 2,
  4901. x + w, y + h - r
  4902. );
  4903. }
  4904. // Anchor on left side
  4905. } else if (anchorX && anchorX < 0) {
  4906. // Chevron
  4907. if (anchorY > y + safeDistance && anchorY < y + h - safeDistance) {
  4908. path.splice(33, 3,
  4909. 'L', x, anchorY + halfDistance,
  4910. x - arrowLength, anchorY,
  4911. x, anchorY - halfDistance,
  4912. x, y + r
  4913. );
  4914. // Simple connector
  4915. } else {
  4916. path.splice(33, 3,
  4917. 'L', x, h / 2,
  4918. anchorX, anchorY,
  4919. x, h / 2,
  4920. x, y + r
  4921. );
  4922. }
  4923. } else if (anchorY && anchorY > h && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace bottom
  4924. path.splice(23, 3,
  4925. 'L', anchorX + halfDistance, y + h,
  4926. anchorX, y + h + arrowLength,
  4927. anchorX - halfDistance, y + h,
  4928. x + r, y + h
  4929. );
  4930. } else if (anchorY && anchorY < 0 && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace top
  4931. path.splice(3, 3,
  4932. 'L', anchorX - halfDistance, y,
  4933. anchorX, y - arrowLength,
  4934. anchorX + halfDistance, y,
  4935. w - r, y
  4936. );
  4937. }
  4938. return path;
  4939. }
  4940. },
  4941. /**
  4942. * @typedef {Highcharts.SVGElement} ClipRect - A clipping rectangle that can be applied
  4943. * to one or more {@link SVGElement} instances. It is instanciated with the
  4944. * {@link SVGRenderer#clipRect} function and applied with the {@link
  4945. * SVGElement#clip} function.
  4946. *
  4947. * @example
  4948. * var circle = renderer.circle(100, 100, 100)
  4949. * .attr({ fill: 'red' })
  4950. * .add();
  4951. * var clipRect = renderer.clipRect(100, 100, 100, 100);
  4952. *
  4953. * // Leave only the lower right quarter visible
  4954. * circle.clip(clipRect);
  4955. */
  4956. /**
  4957. * Define a clipping rectangle
  4958. * @param {String} id
  4959. * @param {number} x
  4960. * @param {number} y
  4961. * @param {number} width
  4962. * @param {number} height
  4963. * @returns {ClipRect} A clipping rectangle.
  4964. */
  4965. clipRect: function(x, y, width, height) {
  4966. var wrapper,
  4967. id = H.uniqueKey(),
  4968. clipPath = this.createElement('clipPath').attr({
  4969. id: id
  4970. }).add(this.defs);
  4971. wrapper = this.rect(x, y, width, height, 0).add(clipPath);
  4972. wrapper.id = id;
  4973. wrapper.clipPath = clipPath;
  4974. wrapper.count = 0;
  4975. return wrapper;
  4976. },
  4977. /**
  4978. * Draw text. The text can contain a subset of HTML, like spans and anchors
  4979. * and some basic text styling of these. For more advanced features like
  4980. * border and background, use {@link Highcharts.SVGRenderer#label} instead.
  4981. * To update the text after render, run `text.attr({ text: 'New text' })`.
  4982. * @param {String} str
  4983. * The text of (subset) HTML to draw.
  4984. * @param {number} x
  4985. * The x position of the text's lower left corner.
  4986. * @param {number} y
  4987. * The y position of the text's lower left corner.
  4988. * @param {Boolean} [useHTML=false]
  4989. * Use HTML to render the text.
  4990. *
  4991. * @return {Highcharts.SVGElement} The text object.
  4992. *
  4993. * @sample highcharts/members/renderer-text-on-chart/
  4994. * Annotate the chart freely
  4995. * @sample highcharts/members/renderer-on-chart/
  4996. * Annotate with a border and in response to the data
  4997. * @sample highcharts/members/renderer-text/
  4998. * Formatted text
  4999. */
  5000. text: function(str, x, y, useHTML) {
  5001. // declare variables
  5002. var renderer = this,
  5003. fakeSVG = !svg && renderer.forExport,
  5004. wrapper,
  5005. attribs = {};
  5006. if (useHTML && (renderer.allowHTML || !renderer.forExport)) {
  5007. return renderer.html(str, x, y);
  5008. }
  5009. attribs.x = Math.round(x || 0); // X is always needed for line-wrap logic
  5010. if (y) {
  5011. attribs.y = Math.round(y);
  5012. }
  5013. if (str || str === 0) {
  5014. attribs.text = str;
  5015. }
  5016. wrapper = renderer.createElement('text')
  5017. .attr(attribs);
  5018. // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063)
  5019. if (fakeSVG) {
  5020. wrapper.css({
  5021. position: 'absolute'
  5022. });
  5023. }
  5024. if (!useHTML) {
  5025. wrapper.xSetter = function(value, key, element) {
  5026. var tspans = element.getElementsByTagName('tspan'),
  5027. tspan,
  5028. parentVal = element.getAttribute(key),
  5029. i;
  5030. for (i = 0; i < tspans.length; i++) {
  5031. tspan = tspans[i];
  5032. // If the x values are equal, the tspan represents a linebreak
  5033. if (tspan.getAttribute(key) === parentVal) {
  5034. tspan.setAttribute(key, value);
  5035. }
  5036. }
  5037. element.setAttribute(key, value);
  5038. };
  5039. }
  5040. return wrapper;
  5041. },
  5042. /**
  5043. * Utility to return the baseline offset and total line height from the font
  5044. * size.
  5045. *
  5046. * @param {?string} fontSize The current font size to inspect. If not given,
  5047. * the font size will be found from the DOM element.
  5048. * @param {SVGElement|SVGDOMElement} [elem] The element to inspect for a
  5049. * current font size.
  5050. * @returns {Object} An object containing `h`: the line height, `b`: the
  5051. * baseline relative to the top of the box, and `f`: the font size.
  5052. */
  5053. fontMetrics: function(fontSize, elem) {
  5054. var lineHeight,
  5055. baseline;
  5056. fontSize = fontSize ||
  5057. // When the elem is a DOM element (#5932)
  5058. (elem && elem.style && elem.style.fontSize) ||
  5059. // Fall back on the renderer style default
  5060. (this.style && this.style.fontSize);
  5061. // Handle different units
  5062. if (/px/.test(fontSize)) {
  5063. fontSize = pInt(fontSize);
  5064. } else if (/em/.test(fontSize)) {
  5065. // The em unit depends on parent items
  5066. fontSize = parseFloat(fontSize) *
  5067. (elem ? this.fontMetrics(null, elem.parentNode).f : 16);
  5068. } else {
  5069. fontSize = 12;
  5070. }
  5071. // Empirical values found by comparing font size and bounding box
  5072. // height. Applies to the default font family.
  5073. // http://jsfiddle.net/highcharts/7xvn7/
  5074. lineHeight = fontSize < 24 ? fontSize + 3 : Math.round(fontSize * 1.2);
  5075. baseline = Math.round(lineHeight * 0.8);
  5076. return {
  5077. h: lineHeight,
  5078. b: baseline,
  5079. f: fontSize
  5080. };
  5081. },
  5082. /**
  5083. * Correct X and Y positioning of a label for rotation (#1764)
  5084. */
  5085. rotCorr: function(baseline, rotation, alterY) {
  5086. var y = baseline;
  5087. if (rotation && alterY) {
  5088. y = Math.max(y * Math.cos(rotation * deg2rad), 4);
  5089. }
  5090. return {
  5091. x: (-baseline / 3) * Math.sin(rotation * deg2rad),
  5092. y: y
  5093. };
  5094. },
  5095. /**
  5096. * Draw a label, which is an extended text element with support for border
  5097. * and background. Highcharts creates a `g` element with a text and a `path`
  5098. * or `rect` inside, to make it behave somewhat like a HTML div. Border and
  5099. * background are set through `stroke`, `stroke-width` and `fill` attributes
  5100. * using the {@link Highcharts.SVGElement#attr|attr} method. To update the
  5101. * text after render, run `label.attr({ text: 'New text' })`.
  5102. *
  5103. * @param {string} str
  5104. * The initial text string or (subset) HTML to render.
  5105. * @param {number} x
  5106. * The x position of the label's left side.
  5107. * @param {number} y
  5108. * The y position of the label's top side or baseline, depending on
  5109. * the `baseline` parameter.
  5110. * @param {String} shape
  5111. * The shape of the label's border/background, if any. Defaults to
  5112. * `rect`. Other possible values are `callout` or other shapes
  5113. * defined in {@link Highcharts.SVGRenderer#symbols}.
  5114. * @param {number} anchorX
  5115. * In case the `shape` has a pointer, like a flag, this is the
  5116. * coordinates it should be pinned to.
  5117. * @param {number} anchorY
  5118. * In case the `shape` has a pointer, like a flag, this is the
  5119. * coordinates it should be pinned to.
  5120. * @param {Boolean} baseline
  5121. * Whether to position the label relative to the text baseline,
  5122. * like {@link Highcharts.SVGRenderer#text|renderer.text}, or to the
  5123. * upper border of the rectangle.
  5124. * @param {String} className
  5125. * Class name for the group.
  5126. *
  5127. * @return {Highcharts.SVGElement}
  5128. * The generated label.
  5129. *
  5130. * @sample highcharts/members/renderer-label-on-chart/
  5131. * A label on the chart
  5132. */
  5133. label: function(str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {
  5134. var renderer = this,
  5135. wrapper = renderer.g(className !== 'button' && 'label'),
  5136. text = wrapper.text = renderer.text('', 0, 0, useHTML)
  5137. .attr({
  5138. zIndex: 1
  5139. }),
  5140. box,
  5141. bBox,
  5142. alignFactor = 0,
  5143. padding = 3,
  5144. paddingLeft = 0,
  5145. width,
  5146. height,
  5147. wrapperX,
  5148. wrapperY,
  5149. textAlign,
  5150. deferredAttr = {},
  5151. strokeWidth,
  5152. baselineOffset,
  5153. hasBGImage = /^url\((.*?)\)$/.test(shape),
  5154. needsBox = hasBGImage,
  5155. getCrispAdjust,
  5156. updateBoxSize,
  5157. updateTextPadding,
  5158. boxAttr;
  5159. if (className) {
  5160. wrapper.addClass('highcharts-' + className);
  5161. }
  5162. needsBox = hasBGImage;
  5163. getCrispAdjust = function() {
  5164. return (strokeWidth || 0) % 2 / 2;
  5165. };
  5166. /**
  5167. * This function runs after the label is added to the DOM (when the bounding box is
  5168. * available), and after the text of the label is updated to detect the new bounding
  5169. * box and reflect it in the border box.
  5170. */
  5171. updateBoxSize = function() {
  5172. var style = text.element.style,
  5173. crispAdjust,
  5174. attribs = {};
  5175. bBox = (width === undefined || height === undefined || textAlign) && defined(text.textStr) &&
  5176. text.getBBox(); //#3295 && 3514 box failure when string equals 0
  5177. wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft;
  5178. wrapper.height = (height || bBox.height || 0) + 2 * padding;
  5179. // Update the label-scoped y offset
  5180. baselineOffset = padding + renderer.fontMetrics(style && style.fontSize, text).b;
  5181. if (needsBox) {
  5182. // Create the border box if it is not already present
  5183. if (!box) {
  5184. wrapper.box = box = renderer.symbols[shape] || hasBGImage ? // Symbol definition exists (#5324)
  5185. renderer.symbol(shape) :
  5186. renderer.rect();
  5187. box.addClass(
  5188. (className === 'button' ? '' : 'highcharts-label-box') + // Don't use label className for buttons
  5189. (className ? ' highcharts-' + className + '-box' : '')
  5190. );
  5191. box.add(wrapper);
  5192. crispAdjust = getCrispAdjust();
  5193. attribs.x = crispAdjust;
  5194. attribs.y = (baseline ? -baselineOffset : 0) + crispAdjust;
  5195. }
  5196. // Apply the box attributes
  5197. attribs.width = Math.round(wrapper.width);
  5198. attribs.height = Math.round(wrapper.height);
  5199. box.attr(extend(attribs, deferredAttr));
  5200. deferredAttr = {};
  5201. }
  5202. };
  5203. /**
  5204. * This function runs after setting text or padding, but only if padding is changed
  5205. */
  5206. updateTextPadding = function() {
  5207. var textX = paddingLeft + padding,
  5208. textY;
  5209. // determin y based on the baseline
  5210. textY = baseline ? 0 : baselineOffset;
  5211. // compensate for alignment
  5212. if (defined(width) && bBox && (textAlign === 'center' || textAlign === 'right')) {
  5213. textX += {
  5214. center: 0.5,
  5215. right: 1
  5216. }[textAlign] * (width - bBox.width);
  5217. }
  5218. // update if anything changed
  5219. if (textX !== text.x || textY !== text.y) {
  5220. text.attr('x', textX);
  5221. if (textY !== undefined) {
  5222. text.attr('y', textY);
  5223. }
  5224. }
  5225. // record current values
  5226. text.x = textX;
  5227. text.y = textY;
  5228. };
  5229. /**
  5230. * Set a box attribute, or defer it if the box is not yet created
  5231. * @param {Object} key
  5232. * @param {Object} value
  5233. */
  5234. boxAttr = function(key, value) {
  5235. if (box) {
  5236. box.attr(key, value);
  5237. } else {
  5238. deferredAttr[key] = value;
  5239. }
  5240. };
  5241. /**
  5242. * After the text element is added, get the desired size of the border box
  5243. * and add it before the text in the DOM.
  5244. */
  5245. wrapper.onAdd = function() {
  5246. text.add(wrapper);
  5247. wrapper.attr({
  5248. text: (str || str === 0) ? str : '', // alignment is available now // #3295: 0 not rendered if given as a value
  5249. x: x,
  5250. y: y
  5251. });
  5252. if (box && defined(anchorX)) {
  5253. wrapper.attr({
  5254. anchorX: anchorX,
  5255. anchorY: anchorY
  5256. });
  5257. }
  5258. };
  5259. /*
  5260. * Add specific attribute setters.
  5261. */
  5262. // only change local variables
  5263. wrapper.widthSetter = function(value) {
  5264. width = H.isNumber(value) ? value : null; // width:auto => null
  5265. };
  5266. wrapper.heightSetter = function(value) {
  5267. height = value;
  5268. };
  5269. wrapper['text-alignSetter'] = function(value) {
  5270. textAlign = value;
  5271. };
  5272. wrapper.paddingSetter = function(value) {
  5273. if (defined(value) && value !== padding) {
  5274. padding = wrapper.padding = value;
  5275. updateTextPadding();
  5276. }
  5277. };
  5278. wrapper.paddingLeftSetter = function(value) {
  5279. if (defined(value) && value !== paddingLeft) {
  5280. paddingLeft = value;
  5281. updateTextPadding();
  5282. }
  5283. };
  5284. // change local variable and prevent setting attribute on the group
  5285. wrapper.alignSetter = function(value) {
  5286. value = {
  5287. left: 0,
  5288. center: 0.5,
  5289. right: 1
  5290. }[value];
  5291. if (value !== alignFactor) {
  5292. alignFactor = value;
  5293. if (bBox) { // Bounding box exists, means we're dynamically changing
  5294. wrapper.attr({
  5295. x: wrapperX
  5296. }); // #5134
  5297. }
  5298. }
  5299. };
  5300. // apply these to the box and the text alike
  5301. wrapper.textSetter = function(value) {
  5302. if (value !== undefined) {
  5303. text.textSetter(value);
  5304. }
  5305. updateBoxSize();
  5306. updateTextPadding();
  5307. };
  5308. // apply these to the box but not to the text
  5309. wrapper['stroke-widthSetter'] = function(value, key) {
  5310. if (value) {
  5311. needsBox = true;
  5312. }
  5313. strokeWidth = this['stroke-width'] = value;
  5314. boxAttr(key, value);
  5315. };
  5316. wrapper.strokeSetter = wrapper.fillSetter = wrapper.rSetter = function(value, key) {
  5317. if (key === 'fill' && value) {
  5318. needsBox = true;
  5319. }
  5320. boxAttr(key, value);
  5321. };
  5322. wrapper.anchorXSetter = function(value, key) {
  5323. anchorX = wrapper.anchorX = value;
  5324. boxAttr(key, Math.round(value) - getCrispAdjust() - wrapperX);
  5325. };
  5326. wrapper.anchorYSetter = function(value, key) {
  5327. anchorY = wrapper.anchorY = value;
  5328. boxAttr(key, value - wrapperY);
  5329. };
  5330. // rename attributes
  5331. wrapper.xSetter = function(value) {
  5332. wrapper.x = value; // for animation getter
  5333. if (alignFactor) {
  5334. value -= alignFactor * ((width || bBox.width) + 2 * padding);
  5335. }
  5336. wrapperX = Math.round(value);
  5337. wrapper.attr('translateX', wrapperX);
  5338. };
  5339. wrapper.ySetter = function(value) {
  5340. wrapperY = wrapper.y = Math.round(value);
  5341. wrapper.attr('translateY', wrapperY);
  5342. };
  5343. // Redirect certain methods to either the box or the text
  5344. var baseCss = wrapper.css;
  5345. return extend(wrapper, {
  5346. /**
  5347. * Pick up some properties and apply them to the text instead of the
  5348. * wrapper.
  5349. * @ignore
  5350. */
  5351. css: function(styles) {
  5352. if (styles) {
  5353. var textStyles = {};
  5354. styles = merge(styles); // create a copy to avoid altering the original object (#537)
  5355. each(wrapper.textProps, function(prop) {
  5356. if (styles[prop] !== undefined) {
  5357. textStyles[prop] = styles[prop];
  5358. delete styles[prop];
  5359. }
  5360. });
  5361. text.css(textStyles);
  5362. }
  5363. return baseCss.call(wrapper, styles);
  5364. },
  5365. /**
  5366. * Return the bounding box of the box, not the group.
  5367. * @ignore
  5368. */
  5369. getBBox: function() {
  5370. return {
  5371. width: bBox.width + 2 * padding,
  5372. height: bBox.height + 2 * padding,
  5373. x: bBox.x - padding,
  5374. y: bBox.y - padding
  5375. };
  5376. },
  5377. /**
  5378. * Apply the shadow to the box.
  5379. * @ignore
  5380. */
  5381. shadow: function(b) {
  5382. if (b) {
  5383. updateBoxSize();
  5384. if (box) {
  5385. box.shadow(b);
  5386. }
  5387. }
  5388. return wrapper;
  5389. },
  5390. /**
  5391. * Destroy and release memory.
  5392. * @ignore
  5393. */
  5394. destroy: function() {
  5395. // Added by button implementation
  5396. removeEvent(wrapper.element, 'mouseenter');
  5397. removeEvent(wrapper.element, 'mouseleave');
  5398. if (text) {
  5399. text = text.destroy();
  5400. }
  5401. if (box) {
  5402. box = box.destroy();
  5403. }
  5404. // Call base implementation to destroy the rest
  5405. SVGElement.prototype.destroy.call(wrapper);
  5406. // Release local pointers (#1298)
  5407. wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = null;
  5408. }
  5409. });
  5410. }
  5411. }); // end SVGRenderer
  5412. // general renderer
  5413. H.Renderer = SVGRenderer;
  5414. }(Highcharts));
  5415. (function(H) {
  5416. /**
  5417. * (c) 2010-2017 Torstein Honsi
  5418. *
  5419. * License: www.highcharts.com/license
  5420. */
  5421. var attr = H.attr,
  5422. createElement = H.createElement,
  5423. css = H.css,
  5424. defined = H.defined,
  5425. each = H.each,
  5426. extend = H.extend,
  5427. isFirefox = H.isFirefox,
  5428. isMS = H.isMS,
  5429. isWebKit = H.isWebKit,
  5430. pInt = H.pInt,
  5431. SVGElement = H.SVGElement,
  5432. SVGRenderer = H.SVGRenderer,
  5433. win = H.win,
  5434. wrap = H.wrap;
  5435. // Extend SvgElement for useHTML option
  5436. extend(SVGElement.prototype, /** @lends SVGElement.prototype */ {
  5437. /**
  5438. * Apply CSS to HTML elements. This is used in text within SVG rendering and
  5439. * by the VML renderer
  5440. */
  5441. htmlCss: function(styles) {
  5442. var wrapper = this,
  5443. element = wrapper.element,
  5444. textWidth = styles && element.tagName === 'SPAN' && styles.width;
  5445. if (textWidth) {
  5446. delete styles.width;
  5447. wrapper.textWidth = textWidth;
  5448. wrapper.updateTransform();
  5449. }
  5450. if (styles && styles.textOverflow === 'ellipsis') {
  5451. styles.whiteSpace = 'nowrap';
  5452. styles.overflow = 'hidden';
  5453. }
  5454. wrapper.styles = extend(wrapper.styles, styles);
  5455. css(wrapper.element, styles);
  5456. return wrapper;
  5457. },
  5458. /**
  5459. * VML and useHTML method for calculating the bounding box based on offsets
  5460. * @param {Boolean} refresh Whether to force a fresh value from the DOM or to
  5461. * use the cached value
  5462. *
  5463. * @return {Object} A hash containing values for x, y, width and height
  5464. */
  5465. htmlGetBBox: function() {
  5466. var wrapper = this,
  5467. element = wrapper.element;
  5468. // faking getBBox in exported SVG in legacy IE
  5469. // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?)
  5470. if (element.nodeName === 'text') {
  5471. element.style.position = 'absolute';
  5472. }
  5473. return {
  5474. x: element.offsetLeft,
  5475. y: element.offsetTop,
  5476. width: element.offsetWidth,
  5477. height: element.offsetHeight
  5478. };
  5479. },
  5480. /**
  5481. * VML override private method to update elements based on internal
  5482. * properties based on SVG transform
  5483. */
  5484. htmlUpdateTransform: function() {
  5485. // aligning non added elements is expensive
  5486. if (!this.added) {
  5487. this.alignOnAdd = true;
  5488. return;
  5489. }
  5490. var wrapper = this,
  5491. renderer = wrapper.renderer,
  5492. elem = wrapper.element,
  5493. translateX = wrapper.translateX || 0,
  5494. translateY = wrapper.translateY || 0,
  5495. x = wrapper.x || 0,
  5496. y = wrapper.y || 0,
  5497. align = wrapper.textAlign || 'left',
  5498. alignCorrection = {
  5499. left: 0,
  5500. center: 0.5,
  5501. right: 1
  5502. }[align],
  5503. styles = wrapper.styles;
  5504. // apply translate
  5505. css(elem, {
  5506. marginLeft: translateX,
  5507. marginTop: translateY
  5508. });
  5509. if (wrapper.shadows) { // used in labels/tooltip
  5510. each(wrapper.shadows, function(shadow) {
  5511. css(shadow, {
  5512. marginLeft: translateX + 1,
  5513. marginTop: translateY + 1
  5514. });
  5515. });
  5516. }
  5517. // apply inversion
  5518. if (wrapper.inverted) { // wrapper is a group
  5519. each(elem.childNodes, function(child) {
  5520. renderer.invertChild(child, elem);
  5521. });
  5522. }
  5523. if (elem.tagName === 'SPAN') {
  5524. var rotation = wrapper.rotation,
  5525. baseline,
  5526. textWidth = pInt(wrapper.textWidth),
  5527. whiteSpace = styles && styles.whiteSpace,
  5528. currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth, wrapper.textAlign].join(',');
  5529. if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
  5530. baseline = renderer.fontMetrics(elem.style.fontSize).b;
  5531. // Renderer specific handling of span rotation
  5532. if (defined(rotation)) {
  5533. wrapper.setSpanRotation(rotation, alignCorrection, baseline);
  5534. }
  5535. // Reset multiline/ellipsis in order to read width (#4928, #5417)
  5536. css(elem, {
  5537. width: '',
  5538. whiteSpace: whiteSpace || 'nowrap'
  5539. });
  5540. // Update textWidth
  5541. if (elem.offsetWidth > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254
  5542. css(elem, {
  5543. width: textWidth + 'px',
  5544. display: 'block',
  5545. whiteSpace: whiteSpace || 'normal' // #3331
  5546. });
  5547. }
  5548. wrapper.getSpanCorrection(elem.offsetWidth, baseline, alignCorrection, rotation, align);
  5549. }
  5550. // apply position with correction
  5551. css(elem, {
  5552. left: (x + (wrapper.xCorr || 0)) + 'px',
  5553. top: (y + (wrapper.yCorr || 0)) + 'px'
  5554. });
  5555. // force reflow in webkit to apply the left and top on useHTML element (#1249)
  5556. if (isWebKit) {
  5557. baseline = elem.offsetHeight; // assigned to baseline for lint purpose
  5558. }
  5559. // record current text transform
  5560. wrapper.cTT = currentTextTransform;
  5561. }
  5562. },
  5563. /**
  5564. * Set the rotation of an individual HTML span
  5565. */
  5566. setSpanRotation: function(rotation, alignCorrection, baseline) {
  5567. var rotationStyle = {},
  5568. cssTransformKey = isMS ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : win.opera ? '-o-transform' : '';
  5569. rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';
  5570. rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] = rotationStyle.transformOrigin = (alignCorrection * 100) + '% ' + baseline + 'px';
  5571. css(this.element, rotationStyle);
  5572. },
  5573. /**
  5574. * Get the correction in X and Y positioning as the element is rotated.
  5575. */
  5576. getSpanCorrection: function(width, baseline, alignCorrection) {
  5577. this.xCorr = -width * alignCorrection;
  5578. this.yCorr = -baseline;
  5579. }
  5580. });
  5581. // Extend SvgRenderer for useHTML option.
  5582. extend(SVGRenderer.prototype, /** @lends SVGRenderer.prototype */ {
  5583. /**
  5584. * Create HTML text node. This is used by the VML renderer as well as the SVG
  5585. * renderer through the useHTML option.
  5586. *
  5587. * @param {String} str
  5588. * @param {Number} x
  5589. * @param {Number} y
  5590. */
  5591. html: function(str, x, y) {
  5592. var wrapper = this.createElement('span'),
  5593. element = wrapper.element,
  5594. renderer = wrapper.renderer,
  5595. isSVG = renderer.isSVG,
  5596. addSetters = function(element, style) {
  5597. // These properties are set as attributes on the SVG group, and as
  5598. // identical CSS properties on the div. (#3542)
  5599. each(['opacity', 'visibility'], function(prop) {
  5600. wrap(element, prop + 'Setter', function(proceed, value, key, elem) {
  5601. proceed.call(this, value, key, elem);
  5602. style[key] = value;
  5603. });
  5604. });
  5605. };
  5606. // Text setter
  5607. wrapper.textSetter = function(value) {
  5608. if (value !== element.innerHTML) {
  5609. delete this.bBox;
  5610. }
  5611. element.innerHTML = this.textStr = value;
  5612. wrapper.htmlUpdateTransform();
  5613. };
  5614. // Add setters for the element itself (#4938)
  5615. if (isSVG) { // #4938, only for HTML within SVG
  5616. addSetters(wrapper, wrapper.element.style);
  5617. }
  5618. // Various setters which rely on update transform
  5619. wrapper.xSetter = wrapper.ySetter = wrapper.alignSetter = wrapper.rotationSetter = function(value, key) {
  5620. if (key === 'align') {
  5621. key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML.
  5622. }
  5623. wrapper[key] = value;
  5624. wrapper.htmlUpdateTransform();
  5625. };
  5626. // Set the default attributes
  5627. wrapper
  5628. .attr({
  5629. text: str,
  5630. x: Math.round(x),
  5631. y: Math.round(y)
  5632. })
  5633. .css({
  5634. fontFamily: this.style.fontFamily,
  5635. fontSize: this.style.fontSize,
  5636. position: 'absolute'
  5637. });
  5638. // Keep the whiteSpace style outside the wrapper.styles collection
  5639. element.style.whiteSpace = 'nowrap';
  5640. // Use the HTML specific .css method
  5641. wrapper.css = wrapper.htmlCss;
  5642. // This is specific for HTML within SVG
  5643. if (isSVG) {
  5644. wrapper.add = function(svgGroupWrapper) {
  5645. var htmlGroup,
  5646. container = renderer.box.parentNode,
  5647. parentGroup,
  5648. parents = [];
  5649. this.parentGroup = svgGroupWrapper;
  5650. // Create a mock group to hold the HTML elements
  5651. if (svgGroupWrapper) {
  5652. htmlGroup = svgGroupWrapper.div;
  5653. if (!htmlGroup) {
  5654. // Read the parent chain into an array and read from top down
  5655. parentGroup = svgGroupWrapper;
  5656. while (parentGroup) {
  5657. parents.push(parentGroup);
  5658. // Move up to the next parent group
  5659. parentGroup = parentGroup.parentGroup;
  5660. }
  5661. // Ensure dynamically updating position when any parent is translated
  5662. each(parents.reverse(), function(parentGroup) {
  5663. var htmlGroupStyle,
  5664. cls = attr(parentGroup.element, 'class');
  5665. if (cls) {
  5666. cls = {
  5667. className: cls
  5668. };
  5669. } // else null
  5670. // Create a HTML div and append it to the parent div to emulate
  5671. // the SVG group structure
  5672. htmlGroup = parentGroup.div = parentGroup.div || createElement('div', cls, {
  5673. position: 'absolute',
  5674. left: (parentGroup.translateX || 0) + 'px',
  5675. top: (parentGroup.translateY || 0) + 'px',
  5676. display: parentGroup.display,
  5677. opacity: parentGroup.opacity, // #5075
  5678. pointerEvents: parentGroup.styles && parentGroup.styles.pointerEvents // #5595
  5679. }, htmlGroup || container); // the top group is appended to container
  5680. // Shortcut
  5681. htmlGroupStyle = htmlGroup.style;
  5682. // Set listeners to update the HTML div's position whenever the SVG group
  5683. // position is changed
  5684. extend(parentGroup, {
  5685. on: function() {
  5686. wrapper.on.apply({
  5687. element: parents[0].div
  5688. }, arguments);
  5689. return parentGroup;
  5690. },
  5691. translateXSetter: function(value, key) {
  5692. htmlGroupStyle.left = value + 'px';
  5693. parentGroup[key] = value;
  5694. parentGroup.doTransform = true;
  5695. },
  5696. translateYSetter: function(value, key) {
  5697. htmlGroupStyle.top = value + 'px';
  5698. parentGroup[key] = value;
  5699. parentGroup.doTransform = true;
  5700. }
  5701. });
  5702. addSetters(parentGroup, htmlGroupStyle);
  5703. });
  5704. }
  5705. } else {
  5706. htmlGroup = container;
  5707. }
  5708. htmlGroup.appendChild(element);
  5709. // Shared with VML:
  5710. wrapper.added = true;
  5711. if (wrapper.alignOnAdd) {
  5712. wrapper.htmlUpdateTransform();
  5713. }
  5714. return wrapper;
  5715. };
  5716. }
  5717. return wrapper;
  5718. }
  5719. });
  5720. }(Highcharts));
  5721. (function(H) {
  5722. /**
  5723. * (c) 2010-2017 Torstein Honsi
  5724. *
  5725. * License: www.highcharts.com/license
  5726. */
  5727. var VMLRenderer,
  5728. VMLRendererExtension,
  5729. VMLElement,
  5730. createElement = H.createElement,
  5731. css = H.css,
  5732. defined = H.defined,
  5733. deg2rad = H.deg2rad,
  5734. discardElement = H.discardElement,
  5735. doc = H.doc,
  5736. each = H.each,
  5737. erase = H.erase,
  5738. extend = H.extend,
  5739. extendClass = H.extendClass,
  5740. isArray = H.isArray,
  5741. isNumber = H.isNumber,
  5742. isObject = H.isObject,
  5743. merge = H.merge,
  5744. noop = H.noop,
  5745. pick = H.pick,
  5746. pInt = H.pInt,
  5747. svg = H.svg,
  5748. SVGElement = H.SVGElement,
  5749. SVGRenderer = H.SVGRenderer,
  5750. win = H.win;
  5751. /* ****************************************************************************
  5752. * *
  5753. * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
  5754. * *
  5755. * For applications and websites that don't need IE support, like platform *
  5756. * targeted mobile apps and web apps, this code can be removed. *
  5757. * *
  5758. *****************************************************************************/
  5759. /**
  5760. * @constructor
  5761. */
  5762. if (!svg) {
  5763. /**
  5764. * The VML element wrapper.
  5765. */
  5766. VMLElement = {
  5767. docMode8: doc && doc.documentMode === 8,
  5768. /**
  5769. * Initialize a new VML element wrapper. It builds the markup as a string
  5770. * to minimize DOM traffic.
  5771. * @param {Object} renderer
  5772. * @param {Object} nodeName
  5773. */
  5774. init: function(renderer, nodeName) {
  5775. var wrapper = this,
  5776. markup = ['<', nodeName, ' filled="f" stroked="f"'],
  5777. style = ['position: ', 'absolute', ';'],
  5778. isDiv = nodeName === 'div';
  5779. // divs and shapes need size
  5780. if (nodeName === 'shape' || isDiv) {
  5781. style.push('left:0;top:0;width:1px;height:1px;');
  5782. }
  5783. style.push('visibility: ', isDiv ? 'hidden' : 'visible');
  5784. markup.push(' style="', style.join(''), '"/>');
  5785. // create element with default attributes and style
  5786. if (nodeName) {
  5787. markup = isDiv || nodeName === 'span' || nodeName === 'img' ?
  5788. markup.join('') :
  5789. renderer.prepVML(markup);
  5790. wrapper.element = createElement(markup);
  5791. }
  5792. wrapper.renderer = renderer;
  5793. },
  5794. /**
  5795. * Add the node to the given parent
  5796. * @param {Object} parent
  5797. */
  5798. add: function(parent) {
  5799. var wrapper = this,
  5800. renderer = wrapper.renderer,
  5801. element = wrapper.element,
  5802. box = renderer.box,
  5803. inverted = parent && parent.inverted,
  5804. // get the parent node
  5805. parentNode = parent ?
  5806. parent.element || parent :
  5807. box;
  5808. if (parent) {
  5809. this.parentGroup = parent;
  5810. }
  5811. // if the parent group is inverted, apply inversion on all children
  5812. if (inverted) { // only on groups
  5813. renderer.invertChild(element, parentNode);
  5814. }
  5815. // append it
  5816. parentNode.appendChild(element);
  5817. // align text after adding to be able to read offset
  5818. wrapper.added = true;
  5819. if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {
  5820. wrapper.updateTransform();
  5821. }
  5822. // fire an event for internal hooks
  5823. if (wrapper.onAdd) {
  5824. wrapper.onAdd();
  5825. }
  5826. // IE8 Standards can't set the class name before the element is appended
  5827. if (this.className) {
  5828. this.attr('class', this.className);
  5829. }
  5830. return wrapper;
  5831. },
  5832. /**
  5833. * VML always uses htmlUpdateTransform
  5834. */
  5835. updateTransform: SVGElement.prototype.htmlUpdateTransform,
  5836. /**
  5837. * Set the rotation of a span with oldIE's filter
  5838. */
  5839. setSpanRotation: function() {
  5840. // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
  5841. // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
  5842. // has support for CSS3 transform. The getBBox method also needs to be updated
  5843. // to compensate for the rotation, like it currently does for SVG.
  5844. // Test case: http://jsfiddle.net/highcharts/Ybt44/
  5845. var rotation = this.rotation,
  5846. costheta = Math.cos(rotation * deg2rad),
  5847. sintheta = Math.sin(rotation * deg2rad);
  5848. css(this.element, {
  5849. filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
  5850. ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
  5851. ', sizingMethod=\'auto expand\')'
  5852. ].join('') : 'none'
  5853. });
  5854. },
  5855. /**
  5856. * Get the positioning correction for the span after rotating.
  5857. */
  5858. getSpanCorrection: function(width, baseline, alignCorrection, rotation, align) {
  5859. var costheta = rotation ? Math.cos(rotation * deg2rad) : 1,
  5860. sintheta = rotation ? Math.sin(rotation * deg2rad) : 0,
  5861. height = pick(this.elemHeight, this.element.offsetHeight),
  5862. quad,
  5863. nonLeft = align && align !== 'left';
  5864. // correct x and y
  5865. this.xCorr = costheta < 0 && -width;
  5866. this.yCorr = sintheta < 0 && -height;
  5867. // correct for baseline and corners spilling out after rotation
  5868. quad = costheta * sintheta < 0;
  5869. this.xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection);
  5870. this.yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
  5871. // correct for the length/height of the text
  5872. if (nonLeft) {
  5873. this.xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
  5874. if (rotation) {
  5875. this.yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
  5876. }
  5877. css(this.element, {
  5878. textAlign: align
  5879. });
  5880. }
  5881. },
  5882. /**
  5883. * Converts a subset of an SVG path definition to its VML counterpart. Takes an array
  5884. * as the parameter and returns a string.
  5885. */
  5886. pathToVML: function(value) {
  5887. // convert paths
  5888. var i = value.length,
  5889. path = [];
  5890. while (i--) {
  5891. // Multiply by 10 to allow subpixel precision.
  5892. // Substracting half a pixel seems to make the coordinates
  5893. // align with SVG, but this hasn't been tested thoroughly
  5894. if (isNumber(value[i])) {
  5895. path[i] = Math.round(value[i] * 10) - 5;
  5896. } else if (value[i] === 'Z') { // close the path
  5897. path[i] = 'x';
  5898. } else {
  5899. path[i] = value[i];
  5900. // When the start X and end X coordinates of an arc are too close,
  5901. // they are rounded to the same value above. In this case, substract or
  5902. // add 1 from the end X and Y positions. #186, #760, #1371, #1410.
  5903. if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) {
  5904. // Start and end X
  5905. if (path[i + 5] === path[i + 7]) {
  5906. path[i + 7] += value[i + 7] > value[i + 5] ? 1 : -1;
  5907. }
  5908. // Start and end Y
  5909. if (path[i + 6] === path[i + 8]) {
  5910. path[i + 8] += value[i + 8] > value[i + 6] ? 1 : -1;
  5911. }
  5912. }
  5913. }
  5914. }
  5915. // Loop up again to handle path shortcuts (#2132)
  5916. /*while (i++ < path.length) {
  5917. if (path[i] === 'H') { // horizontal line to
  5918. path[i] = 'L';
  5919. path.splice(i + 2, 0, path[i - 1]);
  5920. } else if (path[i] === 'V') { // vertical line to
  5921. path[i] = 'L';
  5922. path.splice(i + 1, 0, path[i - 2]);
  5923. }
  5924. }*/
  5925. return path.join(' ') || 'x';
  5926. },
  5927. /**
  5928. * Set the element's clipping to a predefined rectangle
  5929. *
  5930. * @param {String} id The id of the clip rectangle
  5931. */
  5932. clip: function(clipRect) {
  5933. var wrapper = this,
  5934. clipMembers,
  5935. cssRet;
  5936. if (clipRect) {
  5937. clipMembers = clipRect.members;
  5938. erase(clipMembers, wrapper); // Ensure unique list of elements (#1258)
  5939. clipMembers.push(wrapper);
  5940. wrapper.destroyClip = function() {
  5941. erase(clipMembers, wrapper);
  5942. };
  5943. cssRet = clipRect.getCSS(wrapper);
  5944. } else {
  5945. if (wrapper.destroyClip) {
  5946. wrapper.destroyClip();
  5947. }
  5948. cssRet = {
  5949. clip: wrapper.docMode8 ? 'inherit' : 'rect(auto)'
  5950. }; // #1214
  5951. }
  5952. return wrapper.css(cssRet);
  5953. },
  5954. /**
  5955. * Set styles for the element
  5956. * @param {Object} styles
  5957. */
  5958. css: SVGElement.prototype.htmlCss,
  5959. /**
  5960. * Removes a child either by removeChild or move to garbageBin.
  5961. * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
  5962. */
  5963. safeRemoveChild: function(element) {
  5964. // discardElement will detach the node from its parent before attaching it
  5965. // to the garbage bin. Therefore it is important that the node is attached and have parent.
  5966. if (element.parentNode) {
  5967. discardElement(element);
  5968. }
  5969. },
  5970. /**
  5971. * Extend element.destroy by removing it from the clip members array
  5972. */
  5973. destroy: function() {
  5974. if (this.destroyClip) {
  5975. this.destroyClip();
  5976. }
  5977. return SVGElement.prototype.destroy.apply(this);
  5978. },
  5979. /**
  5980. * Add an event listener. VML override for normalizing event parameters.
  5981. * @param {String} eventType
  5982. * @param {Function} handler
  5983. */
  5984. on: function(eventType, handler) {
  5985. // simplest possible event model for internal use
  5986. this.element['on' + eventType] = function() {
  5987. var evt = win.event;
  5988. evt.target = evt.srcElement;
  5989. handler(evt);
  5990. };
  5991. return this;
  5992. },
  5993. /**
  5994. * In stacked columns, cut off the shadows so that they don't overlap
  5995. */
  5996. cutOffPath: function(path, length) {
  5997. var len;
  5998. path = path.split(/[ ,]/); // The extra comma tricks the trailing comma remover in "gulp scripts" task
  5999. len = path.length;
  6000. if (len === 9 || len === 11) {
  6001. path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length;
  6002. }
  6003. return path.join(' ');
  6004. },
  6005. /**
  6006. * Apply a drop shadow by copying elements and giving them different strokes
  6007. * @param {Boolean|Object} shadowOptions
  6008. */
  6009. shadow: function(shadowOptions, group, cutOff) {
  6010. var shadows = [],
  6011. i,
  6012. element = this.element,
  6013. renderer = this.renderer,
  6014. shadow,
  6015. elemStyle = element.style,
  6016. markup,
  6017. path = element.path,
  6018. strokeWidth,
  6019. modifiedPath,
  6020. shadowWidth,
  6021. shadowElementOpacity;
  6022. // some times empty paths are not strings
  6023. if (path && typeof path.value !== 'string') {
  6024. path = 'x';
  6025. }
  6026. modifiedPath = path;
  6027. if (shadowOptions) {
  6028. shadowWidth = pick(shadowOptions.width, 3);
  6029. shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
  6030. for (i = 1; i <= 3; i++) {
  6031. strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
  6032. // Cut off shadows for stacked column items
  6033. if (cutOff) {
  6034. modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5);
  6035. }
  6036. markup = ['<shape isShadow="true" strokeweight="', strokeWidth,
  6037. '" filled="false" path="', modifiedPath,
  6038. '" coordsize="10 10" style="', element.style.cssText, '" />'
  6039. ];
  6040. shadow = createElement(renderer.prepVML(markup),
  6041. null, {
  6042. left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1),
  6043. top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1)
  6044. }
  6045. );
  6046. if (cutOff) {
  6047. shadow.cutOff = strokeWidth + 1;
  6048. }
  6049. // apply the opacity
  6050. markup = [
  6051. '<stroke color="',
  6052. shadowOptions.color || '#000000',
  6053. '" opacity="', shadowElementOpacity * i, '"/>'
  6054. ];
  6055. createElement(renderer.prepVML(markup), null, null, shadow);
  6056. // insert it
  6057. if (group) {
  6058. group.element.appendChild(shadow);
  6059. } else {
  6060. element.parentNode.insertBefore(shadow, element);
  6061. }
  6062. // record it
  6063. shadows.push(shadow);
  6064. }
  6065. this.shadows = shadows;
  6066. }
  6067. return this;
  6068. },
  6069. updateShadows: noop, // Used in SVG only
  6070. setAttr: function(key, value) {
  6071. if (this.docMode8) { // IE8 setAttribute bug
  6072. this.element[key] = value;
  6073. } else {
  6074. this.element.setAttribute(key, value);
  6075. }
  6076. },
  6077. classSetter: function(value) {
  6078. // IE8 Standards mode has problems retrieving the className unless set like this.
  6079. // IE8 Standards can't set the class name before the element is appended.
  6080. (this.added ? this.element : this).className = value;
  6081. },
  6082. dashstyleSetter: function(value, key, element) {
  6083. var strokeElem = element.getElementsByTagName('stroke')[0] ||
  6084. createElement(this.renderer.prepVML(['<stroke/>']), null, null, element);
  6085. strokeElem[key] = value || 'solid';
  6086. this[key] = value;
  6087. /* because changing stroke-width will change the dash length
  6088. and cause an epileptic effect */
  6089. },
  6090. dSetter: function(value, key, element) {
  6091. var i,
  6092. shadows = this.shadows;
  6093. value = value || [];
  6094. this.d = value.join && value.join(' '); // used in getter for animation
  6095. element.path = value = this.pathToVML(value);
  6096. // update shadows
  6097. if (shadows) {
  6098. i = shadows.length;
  6099. while (i--) {
  6100. shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value;
  6101. }
  6102. }
  6103. this.setAttr(key, value);
  6104. },
  6105. fillSetter: function(value, key, element) {
  6106. var nodeName = element.nodeName;
  6107. if (nodeName === 'SPAN') { // text color
  6108. element.style.color = value;
  6109. } else if (nodeName !== 'IMG') { // #1336
  6110. element.filled = value !== 'none';
  6111. this.setAttr('fillcolor', this.renderer.color(value, element, key, this));
  6112. }
  6113. },
  6114. 'fill-opacitySetter': function(value, key, element) {
  6115. createElement(
  6116. this.renderer.prepVML(['<', key.split('-')[0], ' opacity="', value, '"/>']),
  6117. null,
  6118. null,
  6119. element
  6120. );
  6121. },
  6122. opacitySetter: noop, // Don't bother - animation is too slow and filters introduce artifacts
  6123. rotationSetter: function(value, key, element) {
  6124. var style = element.style;
  6125. this[key] = style[key] = value; // style is for #1873
  6126. // Correction for the 1x1 size of the shape container. Used in gauge needles.
  6127. style.left = -Math.round(Math.sin(value * deg2rad) + 1) + 'px';
  6128. style.top = Math.round(Math.cos(value * deg2rad)) + 'px';
  6129. },
  6130. strokeSetter: function(value, key, element) {
  6131. this.setAttr('strokecolor', this.renderer.color(value, element, key, this));
  6132. },
  6133. 'stroke-widthSetter': function(value, key, element) {
  6134. element.stroked = !!value; // VML "stroked" attribute
  6135. this[key] = value; // used in getter, issue #113
  6136. if (isNumber(value)) {
  6137. value += 'px';
  6138. }
  6139. this.setAttr('strokeweight', value);
  6140. },
  6141. titleSetter: function(value, key) {
  6142. this.setAttr(key, value);
  6143. },
  6144. visibilitySetter: function(value, key, element) {
  6145. // Handle inherited visibility
  6146. if (value === 'inherit') {
  6147. value = 'visible';
  6148. }
  6149. // Let the shadow follow the main element
  6150. if (this.shadows) {
  6151. each(this.shadows, function(shadow) {
  6152. shadow.style[key] = value;
  6153. });
  6154. }
  6155. // Instead of toggling the visibility CSS property, move the div out of the viewport.
  6156. // This works around #61 and #586
  6157. if (element.nodeName === 'DIV') {
  6158. value = value === 'hidden' ? '-999em' : 0;
  6159. // In order to redraw, IE7 needs the div to be visible when tucked away
  6160. // outside the viewport. So the visibility is actually opposite of
  6161. // the expected value. This applies to the tooltip only.
  6162. if (!this.docMode8) {
  6163. element.style[key] = value ? 'visible' : 'hidden';
  6164. }
  6165. key = 'top';
  6166. }
  6167. element.style[key] = value;
  6168. },
  6169. xSetter: function(value, key, element) {
  6170. this[key] = value; // used in getter
  6171. if (key === 'x') {
  6172. key = 'left';
  6173. } else if (key === 'y') {
  6174. key = 'top';
  6175. }
  6176. /* else {
  6177. value = Math.max(0, value); // don't set width or height below zero (#311)
  6178. }*/
  6179. // clipping rectangle special
  6180. if (this.updateClipping) {
  6181. this[key] = value; // the key is now 'left' or 'top' for 'x' and 'y'
  6182. this.updateClipping();
  6183. } else {
  6184. // normal
  6185. element.style[key] = value;
  6186. }
  6187. },
  6188. zIndexSetter: function(value, key, element) {
  6189. element.style[key] = value;
  6190. }
  6191. };
  6192. VMLElement['stroke-opacitySetter'] = VMLElement['fill-opacitySetter'];
  6193. H.VMLElement = VMLElement = extendClass(SVGElement, VMLElement);
  6194. // Some shared setters
  6195. VMLElement.prototype.ySetter =
  6196. VMLElement.prototype.widthSetter =
  6197. VMLElement.prototype.heightSetter =
  6198. VMLElement.prototype.xSetter;
  6199. /**
  6200. * The VML renderer
  6201. */
  6202. VMLRendererExtension = { // inherit SVGRenderer
  6203. Element: VMLElement,
  6204. isIE8: win.navigator.userAgent.indexOf('MSIE 8.0') > -1,
  6205. /**
  6206. * Initialize the VMLRenderer
  6207. * @param {Object} container
  6208. * @param {Number} width
  6209. * @param {Number} height
  6210. */
  6211. init: function(container, width, height) {
  6212. var renderer = this,
  6213. boxWrapper,
  6214. box,
  6215. css;
  6216. renderer.alignedObjects = [];
  6217. boxWrapper = renderer.createElement('div')
  6218. .css({
  6219. position: 'relative'
  6220. });
  6221. box = boxWrapper.element;
  6222. container.appendChild(boxWrapper.element);
  6223. // generate the containing box
  6224. renderer.isVML = true;
  6225. renderer.box = box;
  6226. renderer.boxWrapper = boxWrapper;
  6227. renderer.gradients = {};
  6228. renderer.cache = {}; // Cache for numerical bounding boxes
  6229. renderer.cacheKeys = [];
  6230. renderer.imgCount = 0;
  6231. renderer.setSize(width, height, false);
  6232. // The only way to make IE6 and IE7 print is to use a global namespace. However,
  6233. // with IE8 the only way to make the dynamic shapes visible in screen and print mode
  6234. // seems to be to add the xmlns attribute and the behaviour style inline.
  6235. if (!doc.namespaces.hcv) {
  6236. doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
  6237. // Setup default CSS (#2153, #2368, #2384)
  6238. css = 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
  6239. '{ behavior:url(#default#VML); display: inline-block; } ';
  6240. try {
  6241. doc.createStyleSheet().cssText = css;
  6242. } catch (e) {
  6243. doc.styleSheets[0].cssText += css;
  6244. }
  6245. }
  6246. },
  6247. /**
  6248. * Detect whether the renderer is hidden. This happens when one of the parent elements
  6249. * has display: none
  6250. */
  6251. isHidden: function() {
  6252. return !this.box.offsetWidth;
  6253. },
  6254. /**
  6255. * Define a clipping rectangle. In VML it is accomplished by storing the values
  6256. * for setting the CSS style to all associated members.
  6257. *
  6258. * @param {Number} x
  6259. * @param {Number} y
  6260. * @param {Number} width
  6261. * @param {Number} height
  6262. */
  6263. clipRect: function(x, y, width, height) {
  6264. // create a dummy element
  6265. var clipRect = this.createElement(),
  6266. isObj = isObject(x);
  6267. // mimic a rectangle with its style object for automatic updating in attr
  6268. return extend(clipRect, {
  6269. members: [],
  6270. count: 0,
  6271. left: (isObj ? x.x : x) + 1,
  6272. top: (isObj ? x.y : y) + 1,
  6273. width: (isObj ? x.width : width) - 1,
  6274. height: (isObj ? x.height : height) - 1,
  6275. getCSS: function(wrapper) {
  6276. var element = wrapper.element,
  6277. nodeName = element.nodeName,
  6278. isShape = nodeName === 'shape',
  6279. inverted = wrapper.inverted,
  6280. rect = this,
  6281. top = rect.top - (isShape ? element.offsetTop : 0),
  6282. left = rect.left,
  6283. right = left + rect.width,
  6284. bottom = top + rect.height,
  6285. ret = {
  6286. clip: 'rect(' +
  6287. Math.round(inverted ? left : top) + 'px,' +
  6288. Math.round(inverted ? bottom : right) + 'px,' +
  6289. Math.round(inverted ? right : bottom) + 'px,' +
  6290. Math.round(inverted ? top : left) + 'px)'
  6291. };
  6292. // issue 74 workaround
  6293. if (!inverted && wrapper.docMode8 && nodeName === 'DIV') {
  6294. extend(ret, {
  6295. width: right + 'px',
  6296. height: bottom + 'px'
  6297. });
  6298. }
  6299. return ret;
  6300. },
  6301. // used in attr and animation to update the clipping of all members
  6302. updateClipping: function() {
  6303. each(clipRect.members, function(member) {
  6304. // Member.element is falsy on deleted series, like in
  6305. // stock/members/series-remove demo. Should be removed
  6306. // from members, but this will do.
  6307. if (member.element) {
  6308. member.css(clipRect.getCSS(member));
  6309. }
  6310. });
  6311. }
  6312. });
  6313. },
  6314. /**
  6315. * Take a color and return it if it's a string, make it a gradient if it's a
  6316. * gradient configuration object, and apply opacity.
  6317. *
  6318. * @param {Object} color The color or config object
  6319. */
  6320. color: function(color, elem, prop, wrapper) {
  6321. var renderer = this,
  6322. colorObject,
  6323. regexRgba = /^rgba/,
  6324. markup,
  6325. fillType,
  6326. ret = 'none';
  6327. // Check for linear or radial gradient
  6328. if (color && color.linearGradient) {
  6329. fillType = 'gradient';
  6330. } else if (color && color.radialGradient) {
  6331. fillType = 'pattern';
  6332. }
  6333. if (fillType) {
  6334. var stopColor,
  6335. stopOpacity,
  6336. gradient = color.linearGradient || color.radialGradient,
  6337. x1,
  6338. y1,
  6339. x2,
  6340. y2,
  6341. opacity1,
  6342. opacity2,
  6343. color1,
  6344. color2,
  6345. fillAttr = '',
  6346. stops = color.stops,
  6347. firstStop,
  6348. lastStop,
  6349. colors = [],
  6350. addFillNode = function() {
  6351. // Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2
  6352. // are reversed.
  6353. markup = ['<fill colors="' + colors.join(',') +
  6354. '" opacity="', opacity2, '" o:opacity2="',
  6355. opacity1, '" type="', fillType, '" ', fillAttr,
  6356. 'focus="100%" method="any" />'
  6357. ];
  6358. createElement(renderer.prepVML(markup), null, null, elem);
  6359. };
  6360. // Extend from 0 to 1
  6361. firstStop = stops[0];
  6362. lastStop = stops[stops.length - 1];
  6363. if (firstStop[0] > 0) {
  6364. stops.unshift([
  6365. 0,
  6366. firstStop[1]
  6367. ]);
  6368. }
  6369. if (lastStop[0] < 1) {
  6370. stops.push([
  6371. 1,
  6372. lastStop[1]
  6373. ]);
  6374. }
  6375. // Compute the stops
  6376. each(stops, function(stop, i) {
  6377. if (regexRgba.test(stop[1])) {
  6378. colorObject = H.color(stop[1]);
  6379. stopColor = colorObject.get('rgb');
  6380. stopOpacity = colorObject.get('a');
  6381. } else {
  6382. stopColor = stop[1];
  6383. stopOpacity = 1;
  6384. }
  6385. // Build the color attribute
  6386. colors.push((stop[0] * 100) + '% ' + stopColor);
  6387. // Only start and end opacities are allowed, so we use the first and the last
  6388. if (!i) {
  6389. opacity1 = stopOpacity;
  6390. color2 = stopColor;
  6391. } else {
  6392. opacity2 = stopOpacity;
  6393. color1 = stopColor;
  6394. }
  6395. });
  6396. // Apply the gradient to fills only.
  6397. if (prop === 'fill') {
  6398. // Handle linear gradient angle
  6399. if (fillType === 'gradient') {
  6400. x1 = gradient.x1 || gradient[0] || 0;
  6401. y1 = gradient.y1 || gradient[1] || 0;
  6402. x2 = gradient.x2 || gradient[2] || 0;
  6403. y2 = gradient.y2 || gradient[3] || 0;
  6404. fillAttr = 'angle="' + (90 - Math.atan(
  6405. (y2 - y1) / // y vector
  6406. (x2 - x1) // x vector
  6407. ) * 180 / Math.PI) + '"';
  6408. addFillNode();
  6409. // Radial (circular) gradient
  6410. } else {
  6411. var r = gradient.r,
  6412. sizex = r * 2,
  6413. sizey = r * 2,
  6414. cx = gradient.cx,
  6415. cy = gradient.cy,
  6416. radialReference = elem.radialReference,
  6417. bBox,
  6418. applyRadialGradient = function() {
  6419. if (radialReference) {
  6420. bBox = wrapper.getBBox();
  6421. cx += (radialReference[0] - bBox.x) / bBox.width - 0.5;
  6422. cy += (radialReference[1] - bBox.y) / bBox.height - 0.5;
  6423. sizex *= radialReference[2] / bBox.width;
  6424. sizey *= radialReference[2] / bBox.height;
  6425. }
  6426. fillAttr = 'src="' + H.getOptions().global.VMLRadialGradientURL + '" ' +
  6427. 'size="' + sizex + ',' + sizey + '" ' +
  6428. 'origin="0.5,0.5" ' +
  6429. 'position="' + cx + ',' + cy + '" ' +
  6430. 'color2="' + color2 + '" ';
  6431. addFillNode();
  6432. };
  6433. // Apply radial gradient
  6434. if (wrapper.added) {
  6435. applyRadialGradient();
  6436. } else {
  6437. // We need to know the bounding box to get the size and position right
  6438. wrapper.onAdd = applyRadialGradient;
  6439. }
  6440. // The fill element's color attribute is broken in IE8 standards mode, so we
  6441. // need to set the parent shape's fillcolor attribute instead.
  6442. ret = color1;
  6443. }
  6444. // Gradients are not supported for VML stroke, return the first color. #722.
  6445. } else {
  6446. ret = stopColor;
  6447. }
  6448. // If the color is an rgba color, split it and add a fill node
  6449. // to hold the opacity component
  6450. } else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
  6451. colorObject = H.color(color);
  6452. wrapper[prop + '-opacitySetter'](colorObject.get('a'), prop, elem);
  6453. ret = colorObject.get('rgb');
  6454. } else {
  6455. var propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node
  6456. if (propNodes.length) {
  6457. propNodes[0].opacity = 1;
  6458. propNodes[0].type = 'solid';
  6459. }
  6460. ret = color;
  6461. }
  6462. return ret;
  6463. },
  6464. /**
  6465. * Take a VML string and prepare it for either IE8 or IE6/IE7.
  6466. * @param {Array} markup A string array of the VML markup to prepare
  6467. */
  6468. prepVML: function(markup) {
  6469. var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
  6470. isIE8 = this.isIE8;
  6471. markup = markup.join('');
  6472. if (isIE8) { // add xmlns and style inline
  6473. markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
  6474. if (markup.indexOf('style="') === -1) {
  6475. markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
  6476. } else {
  6477. markup = markup.replace('style="', 'style="' + vmlStyle);
  6478. }
  6479. } else { // add namespace
  6480. markup = markup.replace('<', '<hcv:');
  6481. }
  6482. return markup;
  6483. },
  6484. /**
  6485. * Create rotated and aligned text
  6486. * @param {String} str
  6487. * @param {Number} x
  6488. * @param {Number} y
  6489. */
  6490. text: SVGRenderer.prototype.html,
  6491. /**
  6492. * Create and return a path element
  6493. * @param {Array} path
  6494. */
  6495. path: function(path) {
  6496. var attr = {
  6497. // subpixel precision down to 0.1 (width and height = 1px)
  6498. coordsize: '10 10'
  6499. };
  6500. if (isArray(path)) {
  6501. attr.d = path;
  6502. } else if (isObject(path)) { // attributes
  6503. extend(attr, path);
  6504. }
  6505. // create the shape
  6506. return this.createElement('shape').attr(attr);
  6507. },
  6508. /**
  6509. * Create and return a circle element. In VML circles are implemented as
  6510. * shapes, which is faster than v:oval
  6511. * @param {Number} x
  6512. * @param {Number} y
  6513. * @param {Number} r
  6514. */
  6515. circle: function(x, y, r) {
  6516. var circle = this.symbol('circle');
  6517. if (isObject(x)) {
  6518. r = x.r;
  6519. y = x.y;
  6520. x = x.x;
  6521. }
  6522. circle.isCircle = true; // Causes x and y to mean center (#1682)
  6523. circle.r = r;
  6524. return circle.attr({
  6525. x: x,
  6526. y: y
  6527. });
  6528. },
  6529. /**
  6530. * Create a group using an outer div and an inner v:group to allow rotating
  6531. * and flipping. A simple v:group would have problems with positioning
  6532. * child HTML elements and CSS clip.
  6533. *
  6534. * @param {String} name The name of the group
  6535. */
  6536. g: function(name) {
  6537. var wrapper,
  6538. attribs;
  6539. // set the class name
  6540. if (name) {
  6541. attribs = {
  6542. 'className': 'highcharts-' + name,
  6543. 'class': 'highcharts-' + name
  6544. };
  6545. }
  6546. // the div to hold HTML and clipping
  6547. wrapper = this.createElement('div').attr(attribs);
  6548. return wrapper;
  6549. },
  6550. /**
  6551. * VML override to create a regular HTML image
  6552. * @param {String} src
  6553. * @param {Number} x
  6554. * @param {Number} y
  6555. * @param {Number} width
  6556. * @param {Number} height
  6557. */
  6558. image: function(src, x, y, width, height) {
  6559. var obj = this.createElement('img')
  6560. .attr({
  6561. src: src
  6562. });
  6563. if (arguments.length > 1) {
  6564. obj.attr({
  6565. x: x,
  6566. y: y,
  6567. width: width,
  6568. height: height
  6569. });
  6570. }
  6571. return obj;
  6572. },
  6573. /**
  6574. * For rectangles, VML uses a shape for rect to overcome bugs and rotation problems
  6575. */
  6576. createElement: function(nodeName) {
  6577. return nodeName === 'rect' ?
  6578. this.symbol(nodeName) :
  6579. SVGRenderer.prototype.createElement.call(this, nodeName);
  6580. },
  6581. /**
  6582. * In the VML renderer, each child of an inverted div (group) is inverted
  6583. * @param {Object} element
  6584. * @param {Object} parentNode
  6585. */
  6586. invertChild: function(element, parentNode) {
  6587. var ren = this,
  6588. parentStyle = parentNode.style,
  6589. imgStyle = element.tagName === 'IMG' && element.style; // #1111
  6590. css(element, {
  6591. flip: 'x',
  6592. left: pInt(parentStyle.width) - (imgStyle ? pInt(imgStyle.top) : 1),
  6593. top: pInt(parentStyle.height) - (imgStyle ? pInt(imgStyle.left) : 1),
  6594. rotation: -90
  6595. });
  6596. // Recursively invert child elements, needed for nested composite
  6597. // shapes like box plots and error bars. #1680, #1806.
  6598. each(element.childNodes, function(child) {
  6599. ren.invertChild(child, element);
  6600. });
  6601. },
  6602. /**
  6603. * Symbol definitions that override the parent SVG renderer's symbols
  6604. *
  6605. */
  6606. symbols: {
  6607. // VML specific arc function
  6608. arc: function(x, y, w, h, options) {
  6609. var start = options.start,
  6610. end = options.end,
  6611. radius = options.r || w || h,
  6612. innerRadius = options.innerR,
  6613. cosStart = Math.cos(start),
  6614. sinStart = Math.sin(start),
  6615. cosEnd = Math.cos(end),
  6616. sinEnd = Math.sin(end),
  6617. ret;
  6618. if (end - start === 0) { // no angle, don't show it.
  6619. return ['x'];
  6620. }
  6621. ret = [
  6622. 'wa', // clockwise arc to
  6623. x - radius, // left
  6624. y - radius, // top
  6625. x + radius, // right
  6626. y + radius, // bottom
  6627. x + radius * cosStart, // start x
  6628. y + radius * sinStart, // start y
  6629. x + radius * cosEnd, // end x
  6630. y + radius * sinEnd // end y
  6631. ];
  6632. if (options.open && !innerRadius) {
  6633. ret.push(
  6634. 'e',
  6635. 'M',
  6636. x, // - innerRadius,
  6637. y // - innerRadius
  6638. );
  6639. }
  6640. ret.push(
  6641. 'at', // anti clockwise arc to
  6642. x - innerRadius, // left
  6643. y - innerRadius, // top
  6644. x + innerRadius, // right
  6645. y + innerRadius, // bottom
  6646. x + innerRadius * cosEnd, // start x
  6647. y + innerRadius * sinEnd, // start y
  6648. x + innerRadius * cosStart, // end x
  6649. y + innerRadius * sinStart, // end y
  6650. 'x', // finish path
  6651. 'e' // close
  6652. );
  6653. ret.isArc = true;
  6654. return ret;
  6655. },
  6656. // Add circle symbol path. This performs significantly faster than v:oval.
  6657. circle: function(x, y, w, h, wrapper) {
  6658. if (wrapper && defined(wrapper.r)) {
  6659. w = h = 2 * wrapper.r;
  6660. }
  6661. // Center correction, #1682
  6662. if (wrapper && wrapper.isCircle) {
  6663. x -= w / 2;
  6664. y -= h / 2;
  6665. }
  6666. // Return the path
  6667. return [
  6668. 'wa', // clockwisearcto
  6669. x, // left
  6670. y, // top
  6671. x + w, // right
  6672. y + h, // bottom
  6673. x + w, // start x
  6674. y + h / 2, // start y
  6675. x + w, // end x
  6676. y + h / 2, // end y
  6677. //'x', // finish path
  6678. 'e' // close
  6679. ];
  6680. },
  6681. /**
  6682. * Add rectangle symbol path which eases rotation and omits arcsize problems
  6683. * compared to the built-in VML roundrect shape. When borders are not rounded,
  6684. * use the simpler square path, else use the callout path without the arrow.
  6685. */
  6686. rect: function(x, y, w, h, options) {
  6687. return SVGRenderer.prototype.symbols[!defined(options) || !options.r ? 'square' : 'callout'].call(0, x, y, w, h, options);
  6688. }
  6689. }
  6690. };
  6691. H.VMLRenderer = VMLRenderer = function() {
  6692. this.init.apply(this, arguments);
  6693. };
  6694. VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);
  6695. // general renderer
  6696. H.Renderer = VMLRenderer;
  6697. }
  6698. // This method is used with exporting in old IE, when emulating SVG (see #2314)
  6699. SVGRenderer.prototype.measureSpanWidth = function(text, styles) {
  6700. var measuringSpan = doc.createElement('span'),
  6701. offsetWidth,
  6702. textNode = doc.createTextNode(text);
  6703. measuringSpan.appendChild(textNode);
  6704. css(measuringSpan, styles);
  6705. this.box.appendChild(measuringSpan);
  6706. offsetWidth = measuringSpan.offsetWidth;
  6707. discardElement(measuringSpan); // #2463
  6708. return offsetWidth;
  6709. };
  6710. /* ****************************************************************************
  6711. * *
  6712. * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
  6713. * *
  6714. *****************************************************************************/
  6715. }(Highcharts));
  6716. (function(H) {
  6717. /**
  6718. * (c) 2010-2017 Torstein Honsi
  6719. *
  6720. * License: www.highcharts.com/license
  6721. */
  6722. var color = H.color,
  6723. each = H.each,
  6724. getTZOffset = H.getTZOffset,
  6725. isTouchDevice = H.isTouchDevice,
  6726. merge = H.merge,
  6727. pick = H.pick,
  6728. svg = H.svg,
  6729. win = H.win;
  6730. /* ****************************************************************************
  6731. * Handle the options *
  6732. *****************************************************************************/
  6733. H.defaultOptions = {
  6734. colors: '#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1'.split(' '),
  6735. symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
  6736. lang: {
  6737. loading: 'Loading...',
  6738. months: [
  6739. 'January', 'February', 'March', 'April', 'May', 'June', 'July',
  6740. 'August', 'September', 'October', 'November', 'December'
  6741. ],
  6742. shortMonths: [
  6743. 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
  6744. 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
  6745. ],
  6746. weekdays: [
  6747. 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
  6748. 'Thursday', 'Friday', 'Saturday'
  6749. ],
  6750. // invalidDate: '',
  6751. decimalPoint: '.',
  6752. numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels
  6753. resetZoom: 'Reset zoom',
  6754. resetZoomTitle: 'Reset zoom level 1:1',
  6755. thousandsSep: ' '
  6756. },
  6757. global: {
  6758. useUTC: true,
  6759. //timezoneOffset: 0,
  6760. VMLRadialGradientURL: 'http://code.highcharts.com/5.0.12/gfx/vml-radial-gradient.png'
  6761. },
  6762. chart: {
  6763. //animation: true,
  6764. //alignTicks: false,
  6765. //reflow: true,
  6766. //className: null,
  6767. //events: { load, selection },
  6768. //margin: [null],
  6769. //marginTop: null,
  6770. //marginRight: null,
  6771. //marginBottom: null,
  6772. //marginLeft: null,
  6773. borderRadius: 0,
  6774. defaultSeriesType: 'line',
  6775. ignoreHiddenSeries: true,
  6776. //inverted: false,
  6777. spacing: [10, 10, 15, 10],
  6778. //spacingTop: 10,
  6779. //spacingRight: 10,
  6780. //spacingBottom: 15,
  6781. //spacingLeft: 10,
  6782. //zoomType: ''
  6783. resetZoomButton: {
  6784. theme: {
  6785. zIndex: 20
  6786. },
  6787. position: {
  6788. align: 'right',
  6789. x: -10,
  6790. //verticalAlign: 'top',
  6791. y: 10
  6792. }
  6793. // relativeTo: 'plot'
  6794. },
  6795. width: null,
  6796. height: null,
  6797. borderColor: '#335cad',
  6798. //borderWidth: 0,
  6799. //style: {
  6800. // fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
  6801. // fontSize: '12px'
  6802. //},
  6803. backgroundColor: '#ffffff',
  6804. //plotBackgroundColor: null,
  6805. plotBorderColor: '#cccccc'
  6806. //plotBorderWidth: 0,
  6807. //plotShadow: false
  6808. },
  6809. title: {
  6810. text: 'Chart title',
  6811. align: 'center',
  6812. // floating: false,
  6813. margin: 15,
  6814. // x: 0,
  6815. // verticalAlign: 'top',
  6816. // y: null,
  6817. // style: {}, // defined inline
  6818. widthAdjust: -44
  6819. },
  6820. subtitle: {
  6821. text: '',
  6822. align: 'center',
  6823. // floating: false
  6824. // x: 0,
  6825. // verticalAlign: 'top',
  6826. // y: null,
  6827. // style: {}, // defined inline
  6828. widthAdjust: -44
  6829. },
  6830. plotOptions: {},
  6831. labels: {
  6832. //items: [],
  6833. style: {
  6834. //font: defaultFont,
  6835. position: 'absolute',
  6836. color: '#333333'
  6837. }
  6838. },
  6839. legend: {
  6840. enabled: true,
  6841. align: 'center',
  6842. //floating: false,
  6843. layout: 'horizontal',
  6844. labelFormatter: function() {
  6845. return this.name;
  6846. },
  6847. //borderWidth: 0,
  6848. borderColor: '#999999',
  6849. borderRadius: 0,
  6850. navigation: {
  6851. activeColor: '#003399',
  6852. inactiveColor: '#cccccc'
  6853. // animation: true,
  6854. // arrowSize: 12
  6855. // style: {} // text styles
  6856. },
  6857. // margin: 20,
  6858. // reversed: false,
  6859. // backgroundColor: null,
  6860. /*style: {
  6861. padding: '5px'
  6862. },*/
  6863. itemStyle: {
  6864. color: '#333333',
  6865. fontSize: '12px',
  6866. fontWeight: 'bold',
  6867. textOverflow: 'ellipsis'
  6868. },
  6869. itemHoverStyle: {
  6870. //cursor: 'pointer', removed as of #601
  6871. color: '#000000'
  6872. },
  6873. itemHiddenStyle: {
  6874. color: '#cccccc'
  6875. },
  6876. shadow: false,
  6877. itemCheckboxStyle: {
  6878. position: 'absolute',
  6879. width: '13px', // for IE precision
  6880. height: '13px'
  6881. },
  6882. // itemWidth: undefined,
  6883. squareSymbol: true,
  6884. // symbolRadius: 0,
  6885. // symbolWidth: 16,
  6886. symbolPadding: 5,
  6887. verticalAlign: 'bottom',
  6888. // width: undefined,
  6889. x: 0,
  6890. y: 0,
  6891. title: {
  6892. //text: null,
  6893. style: {
  6894. fontWeight: 'bold'
  6895. }
  6896. }
  6897. },
  6898. loading: {
  6899. // hideDuration: 100,
  6900. // showDuration: 0,
  6901. labelStyle: {
  6902. fontWeight: 'bold',
  6903. position: 'relative',
  6904. top: '45%'
  6905. },
  6906. style: {
  6907. position: 'absolute',
  6908. backgroundColor: '#ffffff',
  6909. opacity: 0.5,
  6910. textAlign: 'center'
  6911. }
  6912. },
  6913. tooltip: {
  6914. enabled: true,
  6915. animation: svg,
  6916. //crosshairs: null,
  6917. borderRadius: 3,
  6918. dateTimeLabelFormats: {
  6919. millisecond: '%A, %b %e, %H:%M:%S.%L',
  6920. second: '%A, %b %e, %H:%M:%S',
  6921. minute: '%A, %b %e, %H:%M',
  6922. hour: '%A, %b %e, %H:%M',
  6923. day: '%A, %b %e, %Y',
  6924. week: 'Week from %A, %b %e, %Y',
  6925. month: '%B %Y',
  6926. year: '%Y'
  6927. },
  6928. footerFormat: '',
  6929. //formatter: defaultFormatter,
  6930. /* todo: em font-size when finished comparing against HC4
  6931. headerFormat: '<span style="font-size: 0.85em">{point.key}</span><br/>',
  6932. */
  6933. padding: 8,
  6934. //shape: 'callout',
  6935. //shared: false,
  6936. snap: isTouchDevice ? 25 : 10,
  6937. backgroundColor: color('#f7f7f7').setOpacity(0.85).get(),
  6938. borderWidth: 1,
  6939. headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
  6940. pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>',
  6941. shadow: true,
  6942. style: {
  6943. color: '#333333',
  6944. cursor: 'default',
  6945. fontSize: '12px',
  6946. pointerEvents: 'none', // #1686 http://caniuse.com/#feat=pointer-events
  6947. whiteSpace: 'nowrap'
  6948. }
  6949. //xDateFormat: '%A, %b %e, %Y',
  6950. //valueDecimals: null,
  6951. //valuePrefix: '',
  6952. //valueSuffix: ''
  6953. },
  6954. credits: {
  6955. enabled: true,
  6956. href: 'http://www.highcharts.com',
  6957. position: {
  6958. align: 'right',
  6959. x: -10,
  6960. verticalAlign: 'bottom',
  6961. y: -5
  6962. },
  6963. style: {
  6964. cursor: 'pointer',
  6965. color: '#999999',
  6966. fontSize: '9px'
  6967. },
  6968. text: 'Highcharts.com'
  6969. }
  6970. };
  6971. /**
  6972. * Sets the getTimezoneOffset function. If the timezone option is set, a default
  6973. * getTimezoneOffset function with that timezone is returned. If not, the
  6974. * specified getTimezoneOffset function is returned. If neither are specified,
  6975. * undefined is returned.
  6976. * @return {function} a getTimezoneOffset function or undefined
  6977. */
  6978. function getTimezoneOffsetOption() {
  6979. var globalOptions = H.defaultOptions.global,
  6980. moment = win.moment;
  6981. if (globalOptions.timezone) {
  6982. if (!moment) {
  6983. // getTimezoneOffset-function stays undefined because it depends on
  6984. // Moment.js
  6985. H.error(25);
  6986. } else {
  6987. return function(timestamp) {
  6988. return -moment.tz(
  6989. timestamp,
  6990. globalOptions.timezone
  6991. ).utcOffset();
  6992. };
  6993. }
  6994. }
  6995. // If not timezone is set, look for the getTimezoneOffset callback
  6996. return globalOptions.useUTC && globalOptions.getTimezoneOffset;
  6997. }
  6998. /**
  6999. * Set the time methods globally based on the useUTC option. Time method can be
  7000. * either local time or UTC (default). It is called internally on initiating
  7001. * Highcharts and after running `Highcharts.setOptions`.
  7002. *
  7003. * @private
  7004. */
  7005. function setTimeMethods() {
  7006. var globalOptions = H.defaultOptions.global,
  7007. Date,
  7008. useUTC = globalOptions.useUTC,
  7009. GET = useUTC ? 'getUTC' : 'get',
  7010. SET = useUTC ? 'setUTC' : 'set';
  7011. H.Date = Date = globalOptions.Date || win.Date; // Allow using a different Date class
  7012. Date.hcTimezoneOffset = useUTC && globalOptions.timezoneOffset;
  7013. Date.hcGetTimezoneOffset = getTimezoneOffsetOption();
  7014. Date.hcMakeTime = function(year, month, date, hours, minutes, seconds) {
  7015. var d;
  7016. if (useUTC) {
  7017. d = Date.UTC.apply(0, arguments);
  7018. d += getTZOffset(d);
  7019. } else {
  7020. d = new Date(
  7021. year,
  7022. month,
  7023. pick(date, 1),
  7024. pick(hours, 0),
  7025. pick(minutes, 0),
  7026. pick(seconds, 0)
  7027. ).getTime();
  7028. }
  7029. return d;
  7030. };
  7031. each(['Minutes', 'Hours', 'Day', 'Date', 'Month', 'FullYear'], function(s) {
  7032. Date['hcGet' + s] = GET + s;
  7033. });
  7034. each(['Milliseconds', 'Seconds', 'Minutes', 'Hours', 'Date', 'Month', 'FullYear'], function(s) {
  7035. Date['hcSet' + s] = SET + s;
  7036. });
  7037. }
  7038. /**
  7039. * Merge the default options with custom options and return the new options
  7040. * structure. Commonly used for defining reusable templates.
  7041. *
  7042. * @function #setOptions
  7043. * @memberOf Highcharts
  7044. * @sample highcharts/global/useutc-false Setting a global option
  7045. * @sample highcharts/members/setoptions Applying a global theme
  7046. * @param {Object} options The new custom chart options.
  7047. * @returns {Object} Updated options.
  7048. */
  7049. H.setOptions = function(options) {
  7050. // Copy in the default options
  7051. H.defaultOptions = merge(true, H.defaultOptions, options);
  7052. // Apply UTC
  7053. setTimeMethods();
  7054. return H.defaultOptions;
  7055. };
  7056. /**
  7057. * Get the updated default options. Until 3.0.7, merely exposing defaultOptions for outside modules
  7058. * wasn't enough because the setOptions method created a new object.
  7059. */
  7060. H.getOptions = function() {
  7061. return H.defaultOptions;
  7062. };
  7063. // Series defaults
  7064. H.defaultPlotOptions = H.defaultOptions.plotOptions;
  7065. // set the default time methods
  7066. setTimeMethods();
  7067. }(Highcharts));
  7068. (function(H) {
  7069. /**
  7070. * (c) 2010-2017 Torstein Honsi
  7071. *
  7072. * License: www.highcharts.com/license
  7073. */
  7074. var correctFloat = H.correctFloat,
  7075. defined = H.defined,
  7076. destroyObjectProperties = H.destroyObjectProperties,
  7077. isNumber = H.isNumber,
  7078. merge = H.merge,
  7079. pick = H.pick,
  7080. deg2rad = H.deg2rad;
  7081. /**
  7082. * The Tick class
  7083. */
  7084. H.Tick = function(axis, pos, type, noLabel) {
  7085. this.axis = axis;
  7086. this.pos = pos;
  7087. this.type = type || '';
  7088. this.isNew = true;
  7089. this.isNewLabel = true;
  7090. if (!type && !noLabel) {
  7091. this.addLabel();
  7092. }
  7093. };
  7094. H.Tick.prototype = {
  7095. /**
  7096. * Write the tick label
  7097. */
  7098. addLabel: function() {
  7099. var tick = this,
  7100. axis = tick.axis,
  7101. options = axis.options,
  7102. chart = axis.chart,
  7103. categories = axis.categories,
  7104. names = axis.names,
  7105. pos = tick.pos,
  7106. labelOptions = options.labels,
  7107. str,
  7108. tickPositions = axis.tickPositions,
  7109. isFirst = pos === tickPositions[0],
  7110. isLast = pos === tickPositions[tickPositions.length - 1],
  7111. value = categories ?
  7112. pick(categories[pos], names[pos], pos) :
  7113. pos,
  7114. label = tick.label,
  7115. tickPositionInfo = tickPositions.info,
  7116. dateTimeLabelFormat;
  7117. // Set the datetime label format. If a higher rank is set for this position, use that. If not,
  7118. // use the general format.
  7119. if (axis.isDatetimeAxis && tickPositionInfo) {
  7120. dateTimeLabelFormat =
  7121. options.dateTimeLabelFormats[
  7122. tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName
  7123. ];
  7124. }
  7125. // set properties for access in render method
  7126. tick.isFirst = isFirst;
  7127. tick.isLast = isLast;
  7128. // get the string
  7129. str = axis.labelFormatter.call({
  7130. axis: axis,
  7131. chart: chart,
  7132. isFirst: isFirst,
  7133. isLast: isLast,
  7134. dateTimeLabelFormat: dateTimeLabelFormat,
  7135. value: axis.isLog ? correctFloat(axis.lin2log(value)) : value
  7136. });
  7137. // prepare CSS
  7138. //css = width && { width: Math.max(1, Math.round(width - 2 * (labelOptions.padding || 10))) + 'px' };
  7139. // first call
  7140. if (!defined(label)) {
  7141. tick.label = label =
  7142. defined(str) && labelOptions.enabled ?
  7143. chart.renderer.text(
  7144. str,
  7145. 0,
  7146. 0,
  7147. labelOptions.useHTML
  7148. )
  7149. // without position absolute, IE export sometimes is wrong
  7150. .css(merge(labelOptions.style))
  7151. .add(axis.labelGroup) :
  7152. null;
  7153. tick.labelLength = label && label.getBBox().width; // Un-rotated length
  7154. tick.rotation = 0; // Base value to detect change for new calls to getBBox
  7155. // update
  7156. } else if (label) {
  7157. label.attr({
  7158. text: str
  7159. });
  7160. }
  7161. },
  7162. /**
  7163. * Get the offset height or width of the label
  7164. */
  7165. getLabelSize: function() {
  7166. return this.label ?
  7167. this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] :
  7168. 0;
  7169. },
  7170. /**
  7171. * Handle the label overflow by adjusting the labels to the left and right edge, or
  7172. * hide them if they collide into the neighbour label.
  7173. */
  7174. handleOverflow: function(xy) {
  7175. var axis = this.axis,
  7176. pxPos = xy.x,
  7177. chartWidth = axis.chart.chartWidth,
  7178. spacing = axis.chart.spacing,
  7179. leftBound = pick(axis.labelLeft, Math.min(axis.pos, spacing[3])),
  7180. rightBound = pick(axis.labelRight, Math.max(axis.pos + axis.len, chartWidth - spacing[1])),
  7181. label = this.label,
  7182. rotation = this.rotation,
  7183. factor = {
  7184. left: 0,
  7185. center: 0.5,
  7186. right: 1
  7187. }[axis.labelAlign],
  7188. labelWidth = label.getBBox().width,
  7189. slotWidth = axis.getSlotWidth(),
  7190. modifiedSlotWidth = slotWidth,
  7191. xCorrection = factor,
  7192. goRight = 1,
  7193. leftPos,
  7194. rightPos,
  7195. textWidth,
  7196. css = {};
  7197. // Check if the label overshoots the chart spacing box. If it does, move it.
  7198. // If it now overshoots the slotWidth, add ellipsis.
  7199. if (!rotation) {
  7200. leftPos = pxPos - factor * labelWidth;
  7201. rightPos = pxPos + (1 - factor) * labelWidth;
  7202. if (leftPos < leftBound) {
  7203. modifiedSlotWidth = xy.x + modifiedSlotWidth * (1 - factor) - leftBound;
  7204. } else if (rightPos > rightBound) {
  7205. modifiedSlotWidth = rightBound - xy.x + modifiedSlotWidth * factor;
  7206. goRight = -1;
  7207. }
  7208. modifiedSlotWidth = Math.min(slotWidth, modifiedSlotWidth); // #4177
  7209. if (modifiedSlotWidth < slotWidth && axis.labelAlign === 'center') {
  7210. xy.x += goRight * (slotWidth - modifiedSlotWidth - xCorrection *
  7211. (slotWidth - Math.min(labelWidth, modifiedSlotWidth)));
  7212. }
  7213. // If the label width exceeds the available space, set a text width to be
  7214. // picked up below. Also, if a width has been set before, we need to set a new
  7215. // one because the reported labelWidth will be limited by the box (#3938).
  7216. if (labelWidth > modifiedSlotWidth || (axis.autoRotation && (label.styles || {}).width)) {
  7217. textWidth = modifiedSlotWidth;
  7218. }
  7219. // Add ellipsis to prevent rotated labels to be clipped against the edge of the chart
  7220. } else if (rotation < 0 && pxPos - factor * labelWidth < leftBound) {
  7221. textWidth = Math.round(pxPos / Math.cos(rotation * deg2rad) - leftBound);
  7222. } else if (rotation > 0 && pxPos + factor * labelWidth > rightBound) {
  7223. textWidth = Math.round((chartWidth - pxPos) / Math.cos(rotation * deg2rad));
  7224. }
  7225. if (textWidth) {
  7226. css.width = textWidth;
  7227. if (!(axis.options.labels.style || {}).textOverflow) {
  7228. css.textOverflow = 'ellipsis';
  7229. }
  7230. label.css(css);
  7231. }
  7232. },
  7233. /**
  7234. * Get the x and y position for ticks and labels
  7235. */
  7236. getPosition: function(horiz, pos, tickmarkOffset, old) {
  7237. var axis = this.axis,
  7238. chart = axis.chart,
  7239. cHeight = (old && chart.oldChartHeight) || chart.chartHeight;
  7240. return {
  7241. x: horiz ?
  7242. axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB : axis.left + axis.offset +
  7243. (axis.opposite ?
  7244. ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left :
  7245. 0
  7246. ),
  7247. y: horiz ?
  7248. cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) : cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB
  7249. };
  7250. },
  7251. /**
  7252. * Get the x, y position of the tick label
  7253. */
  7254. getLabelPosition: function(x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
  7255. var axis = this.axis,
  7256. transA = axis.transA,
  7257. reversed = axis.reversed,
  7258. staggerLines = axis.staggerLines,
  7259. rotCorr = axis.tickRotCorr || {
  7260. x: 0,
  7261. y: 0
  7262. },
  7263. yOffset = labelOptions.y,
  7264. line;
  7265. if (!defined(yOffset)) {
  7266. if (axis.side === 0) {
  7267. yOffset = label.rotation ? -8 : -label.getBBox().height;
  7268. } else if (axis.side === 2) {
  7269. yOffset = rotCorr.y + 8;
  7270. } else {
  7271. // #3140, #3140
  7272. yOffset = Math.cos(label.rotation * deg2rad) * (rotCorr.y - label.getBBox(false, 0).height / 2);
  7273. }
  7274. }
  7275. x = x + labelOptions.x + rotCorr.x - (tickmarkOffset && horiz ?
  7276. tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
  7277. y = y + yOffset - (tickmarkOffset && !horiz ?
  7278. tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
  7279. // Correct for staggered labels
  7280. if (staggerLines) {
  7281. line = (index / (step || 1) % staggerLines);
  7282. if (axis.opposite) {
  7283. line = staggerLines - line - 1;
  7284. }
  7285. y += line * (axis.labelOffset / staggerLines);
  7286. }
  7287. return {
  7288. x: x,
  7289. y: Math.round(y)
  7290. };
  7291. },
  7292. /**
  7293. * Extendible method to return the path of the marker
  7294. */
  7295. getMarkPath: function(x, y, tickLength, tickWidth, horiz, renderer) {
  7296. return renderer.crispLine([
  7297. 'M',
  7298. x,
  7299. y,
  7300. 'L',
  7301. x + (horiz ? 0 : -tickLength),
  7302. y + (horiz ? tickLength : 0)
  7303. ], tickWidth);
  7304. },
  7305. /**
  7306. * Renders the gridLine.
  7307. * @param {Boolean} old Whether or not the tick is old
  7308. * @param {number} opacity The opacity of the grid line
  7309. * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
  7310. * @return {undefined}
  7311. */
  7312. renderGridLine: function(old, opacity, reverseCrisp) {
  7313. var tick = this,
  7314. axis = tick.axis,
  7315. options = axis.options,
  7316. gridLine = tick.gridLine,
  7317. gridLinePath,
  7318. attribs = {},
  7319. pos = tick.pos,
  7320. type = tick.type,
  7321. tickmarkOffset = axis.tickmarkOffset,
  7322. renderer = axis.chart.renderer;
  7323. var gridPrefix = type ? type + 'Grid' : 'grid',
  7324. gridLineWidth = options[gridPrefix + 'LineWidth'],
  7325. gridLineColor = options[gridPrefix + 'LineColor'],
  7326. dashStyle = options[gridPrefix + 'LineDashStyle'];
  7327. if (!gridLine) {
  7328. attribs.stroke = gridLineColor;
  7329. attribs['stroke-width'] = gridLineWidth;
  7330. if (dashStyle) {
  7331. attribs.dashstyle = dashStyle;
  7332. }
  7333. if (!type) {
  7334. attribs.zIndex = 1;
  7335. }
  7336. if (old) {
  7337. attribs.opacity = 0;
  7338. }
  7339. tick.gridLine = gridLine = renderer.path()
  7340. .attr(attribs)
  7341. .addClass(
  7342. 'highcharts-' + (type ? type + '-' : '') + 'grid-line'
  7343. )
  7344. .add(axis.gridGroup);
  7345. }
  7346. // If the parameter 'old' is set, the current call will be followed
  7347. // by another call, therefore do not do any animations this time
  7348. if (!old && gridLine) {
  7349. gridLinePath = axis.getPlotLinePath(
  7350. pos + tickmarkOffset,
  7351. gridLine.strokeWidth() * reverseCrisp,
  7352. old, true
  7353. );
  7354. if (gridLinePath) {
  7355. gridLine[tick.isNew ? 'attr' : 'animate']({
  7356. d: gridLinePath,
  7357. opacity: opacity
  7358. });
  7359. }
  7360. }
  7361. },
  7362. /**
  7363. * Renders the tick mark.
  7364. * @param {Object} xy The position vector of the mark
  7365. * @param {number} xy.x The x position of the mark
  7366. * @param {number} xy.y The y position of the mark
  7367. * @param {number} opacity The opacity of the mark
  7368. * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
  7369. * @return {undefined}
  7370. */
  7371. renderMark: function(xy, opacity, reverseCrisp) {
  7372. var tick = this,
  7373. axis = tick.axis,
  7374. options = axis.options,
  7375. renderer = axis.chart.renderer,
  7376. type = tick.type,
  7377. tickPrefix = type ? type + 'Tick' : 'tick',
  7378. tickSize = axis.tickSize(tickPrefix),
  7379. mark = tick.mark,
  7380. isNewMark = !mark,
  7381. x = xy.x,
  7382. y = xy.y;
  7383. var tickWidth = pick(
  7384. options[tickPrefix + 'Width'], !type && axis.isXAxis ? 1 : 0
  7385. ), // X axis defaults to 1
  7386. tickColor = options[tickPrefix + 'Color'];
  7387. if (tickSize) {
  7388. // negate the length
  7389. if (axis.opposite) {
  7390. tickSize[0] = -tickSize[0];
  7391. }
  7392. // First time, create it
  7393. if (isNewMark) {
  7394. tick.mark = mark = renderer.path()
  7395. .addClass('highcharts-' + (type ? type + '-' : '') + 'tick')
  7396. .add(axis.axisGroup);
  7397. mark.attr({
  7398. stroke: tickColor,
  7399. 'stroke-width': tickWidth
  7400. });
  7401. }
  7402. mark[isNewMark ? 'attr' : 'animate']({
  7403. d: tick.getMarkPath(
  7404. x,
  7405. y,
  7406. tickSize[0],
  7407. mark.strokeWidth() * reverseCrisp,
  7408. axis.horiz,
  7409. renderer),
  7410. opacity: opacity
  7411. });
  7412. }
  7413. },
  7414. /**
  7415. * Renders the tick label.
  7416. * Note: The label should already be created in init(), so it should only
  7417. * have to be moved into place.
  7418. * @param {Object} xy The position vector of the label
  7419. * @param {number} xy.x The x position of the label
  7420. * @param {number} xy.y The y position of the label
  7421. * @param {Boolean} old Whether or not the tick is old
  7422. * @param {number} opacity The opacity of the label
  7423. * @param {number} index The index of the tick
  7424. * @return {undefined}
  7425. */
  7426. renderLabel: function(xy, old, opacity, index) {
  7427. var tick = this,
  7428. axis = tick.axis,
  7429. horiz = axis.horiz,
  7430. options = axis.options,
  7431. label = tick.label,
  7432. labelOptions = options.labels,
  7433. step = labelOptions.step,
  7434. tickmarkOffset = axis.tickmarkOffset,
  7435. show = true,
  7436. x = xy.x,
  7437. y = xy.y;
  7438. if (label && isNumber(x)) {
  7439. label.xy = xy = tick.getLabelPosition(
  7440. x,
  7441. y,
  7442. label,
  7443. horiz,
  7444. labelOptions,
  7445. tickmarkOffset,
  7446. index,
  7447. step
  7448. );
  7449. // Apply show first and show last. If the tick is both first and
  7450. // last, it is a single centered tick, in which case we show the
  7451. // label anyway (#2100).
  7452. if (
  7453. (
  7454. tick.isFirst &&
  7455. !tick.isLast &&
  7456. !pick(options.showFirstLabel, 1)
  7457. ) ||
  7458. (
  7459. tick.isLast &&
  7460. !tick.isFirst &&
  7461. !pick(options.showLastLabel, 1)
  7462. )
  7463. ) {
  7464. show = false;
  7465. // Handle label overflow and show or hide accordingly
  7466. } else if (horiz && !axis.isRadial && !labelOptions.step &&
  7467. !labelOptions.rotation && !old && opacity !== 0) {
  7468. tick.handleOverflow(xy);
  7469. }
  7470. // apply step
  7471. if (step && index % step) {
  7472. // show those indices dividable by step
  7473. show = false;
  7474. }
  7475. // Set the new position, and show or hide
  7476. if (show && isNumber(xy.y)) {
  7477. xy.opacity = opacity;
  7478. label[tick.isNewLabel ? 'attr' : 'animate'](xy);
  7479. tick.isNewLabel = false;
  7480. } else {
  7481. label.attr('y', -9999); // #1338
  7482. tick.isNewLabel = true;
  7483. }
  7484. tick.isNew = false;
  7485. }
  7486. },
  7487. /**
  7488. * Put everything in place
  7489. *
  7490. * @param index {Number}
  7491. * @param old {Boolean} Use old coordinates to prepare an animation into new
  7492. * position
  7493. */
  7494. render: function(index, old, opacity) {
  7495. var tick = this,
  7496. axis = tick.axis,
  7497. horiz = axis.horiz,
  7498. pos = tick.pos,
  7499. tickmarkOffset = axis.tickmarkOffset,
  7500. xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
  7501. x = xy.x,
  7502. y = xy.y,
  7503. reverseCrisp = ((horiz && x === axis.pos + axis.len) ||
  7504. (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687
  7505. opacity = pick(opacity, 1);
  7506. this.isActive = true;
  7507. // Create the grid line
  7508. this.renderGridLine(old, opacity, reverseCrisp);
  7509. // create the tick mark
  7510. this.renderMark(xy, opacity, reverseCrisp);
  7511. // the label is created on init - now move it into place
  7512. this.renderLabel(xy, old, opacity, index);
  7513. },
  7514. /**
  7515. * Destructor for the tick prototype
  7516. */
  7517. destroy: function() {
  7518. destroyObjectProperties(this, this.axis);
  7519. }
  7520. };
  7521. }(Highcharts));
  7522. var Axis = (function(H) {
  7523. /**
  7524. * (c) 2010-2017 Torstein Honsi
  7525. *
  7526. * License: www.highcharts.com/license
  7527. */
  7528. var addEvent = H.addEvent,
  7529. animObject = H.animObject,
  7530. arrayMax = H.arrayMax,
  7531. arrayMin = H.arrayMin,
  7532. color = H.color,
  7533. correctFloat = H.correctFloat,
  7534. defaultOptions = H.defaultOptions,
  7535. defined = H.defined,
  7536. deg2rad = H.deg2rad,
  7537. destroyObjectProperties = H.destroyObjectProperties,
  7538. each = H.each,
  7539. extend = H.extend,
  7540. fireEvent = H.fireEvent,
  7541. format = H.format,
  7542. getMagnitude = H.getMagnitude,
  7543. grep = H.grep,
  7544. inArray = H.inArray,
  7545. isArray = H.isArray,
  7546. isNumber = H.isNumber,
  7547. isString = H.isString,
  7548. merge = H.merge,
  7549. normalizeTickInterval = H.normalizeTickInterval,
  7550. objectEach = H.objectEach,
  7551. pick = H.pick,
  7552. removeEvent = H.removeEvent,
  7553. splat = H.splat,
  7554. syncTimeout = H.syncTimeout,
  7555. Tick = H.Tick;
  7556. /**
  7557. * Create a new axis object. Called internally when instanciating a new chart or
  7558. * adding axes by {@link Highcharts.Chart#addAxis}.
  7559. *
  7560. * A chart can have from 0 axes (pie chart) to multiples. In a normal, single
  7561. * series cartesian chart, there is one X axis and one Y axis.
  7562. *
  7563. * The X axis or axes are referenced by {@link Highcharts.Chart.xAxis}, which is
  7564. * an array of Axis objects. If there is only one axis, it can be referenced
  7565. * through `chart.xAxis[0]`, and multiple axes have increasing indices. The same
  7566. * pattern goes for Y axes.
  7567. *
  7568. * If you need to get the axes from a series object, use the `series.xAxis` and
  7569. * `series.yAxis` properties. These are not arrays, as one series can only be
  7570. * associated to one X and one Y axis.
  7571. *
  7572. * A third way to reference the axis programmatically is by `id`. Add an `id` in
  7573. * the axis configuration options, and get the axis by
  7574. * {@link Highcharts.Chart#get}.
  7575. *
  7576. * Configuration options for the axes are given in options.xAxis and
  7577. * options.yAxis.
  7578. *
  7579. * @class Highcharts.Axis
  7580. * @memberOf Highcharts
  7581. * @param {Highcharts.Chart} chart - The Chart instance to apply the axis on.
  7582. * @param {Object} options - Axis options
  7583. */
  7584. var Axis = function() {
  7585. this.init.apply(this, arguments);
  7586. };
  7587. H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
  7588. /**
  7589. * Default options for the X axis - the Y axis has extended defaults.
  7590. *
  7591. * @private
  7592. * @type {Object}
  7593. */
  7594. defaultOptions: {
  7595. // allowDecimals: null,
  7596. // alternateGridColor: null,
  7597. // categories: [],
  7598. dateTimeLabelFormats: {
  7599. millisecond: '%H:%M:%S.%L',
  7600. second: '%H:%M:%S',
  7601. minute: '%H:%M',
  7602. hour: '%H:%M',
  7603. day: '%e. %b',
  7604. week: '%e. %b',
  7605. month: '%b \'%y',
  7606. year: '%Y'
  7607. },
  7608. endOnTick: false,
  7609. // reversed: false,
  7610. labels: {
  7611. enabled: true,
  7612. // rotation: 0,
  7613. // align: 'center',
  7614. // step: null,
  7615. style: {
  7616. color: '#666666',
  7617. cursor: 'default',
  7618. fontSize: '11px'
  7619. },
  7620. x: 0
  7621. //y: undefined
  7622. /*formatter: function () {
  7623. return this.value;
  7624. },*/
  7625. },
  7626. //linkedTo: null,
  7627. //max: undefined,
  7628. //min: undefined,
  7629. minPadding: 0.01,
  7630. maxPadding: 0.01,
  7631. //minRange: null,
  7632. //minorTickInterval: null,
  7633. minorTickLength: 2,
  7634. minorTickPosition: 'outside', // inside or outside
  7635. //opposite: false,
  7636. //offset: 0,
  7637. //plotBands: [{
  7638. // events: {},
  7639. // zIndex: 1,
  7640. // labels: { align, x, verticalAlign, y, style, rotation, textAlign }
  7641. //}],
  7642. //plotLines: [{
  7643. // events: {}
  7644. // dashStyle: {}
  7645. // zIndex:
  7646. // labels: { align, x, verticalAlign, y, style, rotation, textAlign }
  7647. //}],
  7648. //reversed: false,
  7649. // showFirstLabel: true,
  7650. // showLastLabel: true,
  7651. startOfWeek: 1,
  7652. startOnTick: false,
  7653. //tickInterval: null,
  7654. tickLength: 10,
  7655. tickmarkPlacement: 'between', // on or between
  7656. tickPixelInterval: 100,
  7657. tickPosition: 'outside',
  7658. title: {
  7659. //text: null,
  7660. align: 'middle', // low, middle or high
  7661. //margin: 0 for horizontal, 10 for vertical axes,
  7662. // reserveSpace: true,
  7663. //rotation: 0,
  7664. //side: 'outside',
  7665. style: {
  7666. color: '#666666'
  7667. }
  7668. //x: 0,
  7669. //y: 0
  7670. },
  7671. type: 'linear', // linear, logarithmic or datetime
  7672. //visible: true
  7673. minorGridLineColor: '#f2f2f2',
  7674. // minorGridLineDashStyle: null,
  7675. minorGridLineWidth: 1,
  7676. minorTickColor: '#999999',
  7677. //minorTickWidth: 0,
  7678. lineColor: '#ccd6eb',
  7679. lineWidth: 1,
  7680. gridLineColor: '#e6e6e6',
  7681. // gridLineDashStyle: 'solid',
  7682. // gridLineWidth: 0,
  7683. tickColor: '#ccd6eb'
  7684. // tickWidth: 1
  7685. },
  7686. /**
  7687. * This options set extends the defaultOptions for Y axes.
  7688. *
  7689. * @private
  7690. * @type {Object}
  7691. */
  7692. defaultYAxisOptions: {
  7693. endOnTick: true,
  7694. tickPixelInterval: 72,
  7695. showLastLabel: true,
  7696. labels: {
  7697. x: -8
  7698. },
  7699. maxPadding: 0.05,
  7700. minPadding: 0.05,
  7701. startOnTick: true,
  7702. title: {
  7703. rotation: 270,
  7704. text: 'Values'
  7705. },
  7706. stackLabels: {
  7707. enabled: false,
  7708. //align: dynamic,
  7709. //y: dynamic,
  7710. //x: dynamic,
  7711. //verticalAlign: dynamic,
  7712. //textAlign: dynamic,
  7713. //rotation: 0,
  7714. formatter: function() {
  7715. return H.numberFormat(this.total, -1);
  7716. },
  7717. style: {
  7718. fontSize: '11px',
  7719. fontWeight: 'bold',
  7720. color: '#000000',
  7721. textOutline: '1px contrast'
  7722. }
  7723. },
  7724. gridLineWidth: 1,
  7725. lineWidth: 0
  7726. // tickWidth: 0
  7727. },
  7728. /**
  7729. * These options extend the defaultOptions for left axes.
  7730. *
  7731. * @private
  7732. * @type {Object}
  7733. */
  7734. defaultLeftAxisOptions: {
  7735. labels: {
  7736. x: -15
  7737. },
  7738. title: {
  7739. rotation: 270
  7740. }
  7741. },
  7742. /**
  7743. * These options extend the defaultOptions for right axes.
  7744. *
  7745. * @private
  7746. * @type {Object}
  7747. */
  7748. defaultRightAxisOptions: {
  7749. labels: {
  7750. x: 15
  7751. },
  7752. title: {
  7753. rotation: 90
  7754. }
  7755. },
  7756. /**
  7757. * These options extend the defaultOptions for bottom axes.
  7758. *
  7759. * @private
  7760. * @type {Object}
  7761. */
  7762. defaultBottomAxisOptions: {
  7763. labels: {
  7764. autoRotation: [-45],
  7765. x: 0
  7766. // overflow: undefined,
  7767. // staggerLines: null
  7768. },
  7769. title: {
  7770. rotation: 0
  7771. }
  7772. },
  7773. /**
  7774. * These options extend the defaultOptions for top axes.
  7775. *
  7776. * @private
  7777. * @type {Object}
  7778. */
  7779. defaultTopAxisOptions: {
  7780. labels: {
  7781. autoRotation: [-45],
  7782. x: 0
  7783. // overflow: undefined
  7784. // staggerLines: null
  7785. },
  7786. title: {
  7787. rotation: 0
  7788. }
  7789. },
  7790. /**
  7791. * Initialize the axis
  7792. */
  7793. init: function(chart, userOptions) {
  7794. var isXAxis = userOptions.isX,
  7795. axis = this;
  7796. axis.chart = chart;
  7797. // Flag, is the axis horizontal
  7798. axis.horiz = chart.inverted && !axis.isZAxis ? !isXAxis : isXAxis;
  7799. // Flag, isXAxis
  7800. axis.isXAxis = isXAxis;
  7801. axis.coll = axis.coll || (isXAxis ? 'xAxis' : 'yAxis');
  7802. axis.opposite = userOptions.opposite; // needed in setOptions
  7803. axis.side = userOptions.side || (axis.horiz ?
  7804. (axis.opposite ? 0 : 2) : // top : bottom
  7805. (axis.opposite ? 1 : 3)); // right : left
  7806. axis.setOptions(userOptions);
  7807. var options = this.options,
  7808. type = options.type,
  7809. isDatetimeAxis = type === 'datetime';
  7810. axis.labelFormatter = options.labels.formatter ||
  7811. axis.defaultLabelFormatter; // can be overwritten by dynamic format
  7812. // Flag, stagger lines or not
  7813. axis.userOptions = userOptions;
  7814. //axis.axisTitleMargin = undefined,// = options.title.margin,
  7815. axis.minPixelPadding = 0;
  7816. axis.reversed = options.reversed;
  7817. axis.visible = options.visible !== false;
  7818. axis.zoomEnabled = options.zoomEnabled !== false;
  7819. // Initial categories
  7820. axis.hasNames = type === 'category' || options.categories === true;
  7821. axis.categories = options.categories || axis.hasNames;
  7822. axis.names = axis.names || []; // Preserve on update (#3830)
  7823. // Elements
  7824. //axis.axisGroup = undefined;
  7825. //axis.gridGroup = undefined;
  7826. //axis.axisTitle = undefined;
  7827. //axis.axisLine = undefined;
  7828. // Placeholder for plotlines and plotbands groups
  7829. axis.plotLinesAndBandsGroups = {};
  7830. // Shorthand types
  7831. axis.isLog = type === 'logarithmic';
  7832. axis.isDatetimeAxis = isDatetimeAxis;
  7833. axis.positiveValuesOnly = axis.isLog && !axis.allowNegativeLog;
  7834. // Flag, if axis is linked to another axis
  7835. axis.isLinked = defined(options.linkedTo);
  7836. // Linked axis.
  7837. //axis.linkedParent = undefined;
  7838. // Major ticks
  7839. axis.ticks = {};
  7840. axis.labelEdge = [];
  7841. // Minor ticks
  7842. axis.minorTicks = {};
  7843. // List of plotLines/Bands
  7844. axis.plotLinesAndBands = [];
  7845. // Alternate bands
  7846. axis.alternateBands = {};
  7847. // Axis metrics
  7848. //axis.left = undefined;
  7849. //axis.top = undefined;
  7850. //axis.width = undefined;
  7851. //axis.height = undefined;
  7852. //axis.bottom = undefined;
  7853. //axis.right = undefined;
  7854. //axis.transA = undefined;
  7855. //axis.transB = undefined;
  7856. //axis.oldTransA = undefined;
  7857. axis.len = 0;
  7858. //axis.oldMin = undefined;
  7859. //axis.oldMax = undefined;
  7860. //axis.oldUserMin = undefined;
  7861. //axis.oldUserMax = undefined;
  7862. //axis.oldAxisLength = undefined;
  7863. axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
  7864. axis.range = options.range;
  7865. axis.offset = options.offset || 0;
  7866. // Dictionary for stacks
  7867. axis.stacks = {};
  7868. axis.oldStacks = {};
  7869. axis.stacksTouched = 0;
  7870. // Min and max in the data
  7871. //axis.dataMin = undefined,
  7872. //axis.dataMax = undefined,
  7873. // The axis range
  7874. axis.max = null;
  7875. axis.min = null;
  7876. // User set min and max
  7877. //axis.userMin = undefined,
  7878. //axis.userMax = undefined,
  7879. // Crosshair options
  7880. axis.crosshair = pick(
  7881. options.crosshair,
  7882. splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1],
  7883. false
  7884. );
  7885. var events = axis.options.events;
  7886. // Register. Don't add it again on Axis.update().
  7887. if (inArray(axis, chart.axes) === -1) { //
  7888. if (isXAxis) { // #2713
  7889. chart.axes.splice(chart.xAxis.length, 0, axis);
  7890. } else {
  7891. chart.axes.push(axis);
  7892. }
  7893. chart[axis.coll].push(axis);
  7894. }
  7895. axis.series = axis.series || []; // populated by Series
  7896. // inverted charts have reversed xAxes as default
  7897. if (chart.inverted && !axis.isZAxis && isXAxis && axis.reversed === undefined) {
  7898. axis.reversed = true;
  7899. }
  7900. // register event listeners
  7901. objectEach(events, function(event, eventType) {
  7902. addEvent(axis, eventType, event);
  7903. });
  7904. // extend logarithmic axis
  7905. axis.lin2log = options.linearToLogConverter || axis.lin2log;
  7906. if (axis.isLog) {
  7907. axis.val2lin = axis.log2lin;
  7908. axis.lin2val = axis.lin2log;
  7909. }
  7910. },
  7911. /**
  7912. * Merge and set options
  7913. */
  7914. setOptions: function(userOptions) {
  7915. this.options = merge(
  7916. this.defaultOptions,
  7917. this.coll === 'yAxis' && this.defaultYAxisOptions, [
  7918. this.defaultTopAxisOptions,
  7919. this.defaultRightAxisOptions,
  7920. this.defaultBottomAxisOptions,
  7921. this.defaultLeftAxisOptions
  7922. ][this.side],
  7923. merge(
  7924. defaultOptions[this.coll], // if set in setOptions (#1053)
  7925. userOptions
  7926. )
  7927. );
  7928. },
  7929. /**
  7930. * The default label formatter. The context is a special config object for
  7931. * the label. In apps, use the {@link
  7932. * https://api.highcharts.com/highcharts/xAxis.labels.formatter|
  7933. * labels.formatter} instead except when a modification is needed.
  7934. *
  7935. * @private
  7936. */
  7937. defaultLabelFormatter: function() {
  7938. var axis = this.axis,
  7939. value = this.value,
  7940. categories = axis.categories,
  7941. dateTimeLabelFormat = this.dateTimeLabelFormat,
  7942. lang = defaultOptions.lang,
  7943. numericSymbols = lang.numericSymbols,
  7944. numSymMagnitude = lang.numericSymbolMagnitude || 1000,
  7945. i = numericSymbols && numericSymbols.length,
  7946. multi,
  7947. ret,
  7948. formatOption = axis.options.labels.format,
  7949. // make sure the same symbol is added for all labels on a linear
  7950. // axis
  7951. numericSymbolDetector = axis.isLog ?
  7952. Math.abs(value) :
  7953. axis.tickInterval;
  7954. if (formatOption) {
  7955. ret = format(formatOption, this);
  7956. } else if (categories) {
  7957. ret = value;
  7958. } else if (dateTimeLabelFormat) { // datetime axis
  7959. ret = H.dateFormat(dateTimeLabelFormat, value);
  7960. } else if (i && numericSymbolDetector >= 1000) {
  7961. // Decide whether we should add a numeric symbol like k (thousands)
  7962. // or M (millions). If we are to enable this in tooltip or other
  7963. // places as well, we can move this logic to the numberFormatter and
  7964. // enable it by a parameter.
  7965. while (i-- && ret === undefined) {
  7966. multi = Math.pow(numSymMagnitude, i + 1);
  7967. if (
  7968. numericSymbolDetector >= multi &&
  7969. (value * 10) % multi === 0 &&
  7970. numericSymbols[i] !== null &&
  7971. value !== 0
  7972. ) { // #5480
  7973. ret = H.numberFormat(value / multi, -1) + numericSymbols[i];
  7974. }
  7975. }
  7976. }
  7977. if (ret === undefined) {
  7978. if (Math.abs(value) >= 10000) { // add thousands separators
  7979. ret = H.numberFormat(value, -1);
  7980. } else { // small numbers
  7981. ret = H.numberFormat(value, -1, undefined, ''); // #2466
  7982. }
  7983. }
  7984. return ret;
  7985. },
  7986. /**
  7987. * Get the minimum and maximum for the series of each axis
  7988. */
  7989. getSeriesExtremes: function() {
  7990. var axis = this,
  7991. chart = axis.chart;
  7992. axis.hasVisibleSeries = false;
  7993. // Reset properties in case we're redrawing (#3353)
  7994. axis.dataMin = axis.dataMax = axis.threshold = null;
  7995. axis.softThreshold = !axis.isXAxis;
  7996. if (axis.buildStacks) {
  7997. axis.buildStacks();
  7998. }
  7999. // loop through this axis' series
  8000. each(axis.series, function(series) {
  8001. if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
  8002. var seriesOptions = series.options,
  8003. xData,
  8004. threshold = seriesOptions.threshold,
  8005. seriesDataMin,
  8006. seriesDataMax;
  8007. axis.hasVisibleSeries = true;
  8008. // Validate threshold in logarithmic axes
  8009. if (axis.positiveValuesOnly && threshold <= 0) {
  8010. threshold = null;
  8011. }
  8012. // Get dataMin and dataMax for X axes
  8013. if (axis.isXAxis) {
  8014. xData = series.xData;
  8015. if (xData.length) {
  8016. // If xData contains values which is not numbers, then
  8017. // filter them out. To prevent performance hit, we only
  8018. // do this after we have already found seriesDataMin
  8019. // because in most cases all data is valid. #5234.
  8020. seriesDataMin = arrayMin(xData);
  8021. if (!isNumber(seriesDataMin) &&
  8022. !(seriesDataMin instanceof Date) // #5010
  8023. ) {
  8024. xData = grep(xData, function(x) {
  8025. return isNumber(x);
  8026. });
  8027. seriesDataMin = arrayMin(xData); // Do it again with valid data
  8028. }
  8029. axis.dataMin = Math.min(
  8030. pick(axis.dataMin, xData[0]),
  8031. seriesDataMin
  8032. );
  8033. axis.dataMax = Math.max(
  8034. pick(axis.dataMax, xData[0]),
  8035. arrayMax(xData)
  8036. );
  8037. }
  8038. // Get dataMin and dataMax for Y axes, as well as handle
  8039. // stacking and processed data
  8040. } else {
  8041. // Get this particular series extremes
  8042. series.getExtremes();
  8043. seriesDataMax = series.dataMax;
  8044. seriesDataMin = series.dataMin;
  8045. // Get the dataMin and dataMax so far. If percentage is
  8046. // used, the min and max are always 0 and 100. If
  8047. // seriesDataMin and seriesDataMax is null, then series
  8048. // doesn't have active y data, we continue with nulls
  8049. if (defined(seriesDataMin) && defined(seriesDataMax)) {
  8050. axis.dataMin = Math.min(
  8051. pick(axis.dataMin, seriesDataMin),
  8052. seriesDataMin
  8053. );
  8054. axis.dataMax = Math.max(
  8055. pick(axis.dataMax, seriesDataMax),
  8056. seriesDataMax
  8057. );
  8058. }
  8059. // Adjust to threshold
  8060. if (defined(threshold)) {
  8061. axis.threshold = threshold;
  8062. }
  8063. // If any series has a hard threshold, it takes precedence
  8064. if (!seriesOptions.softThreshold ||
  8065. axis.positiveValuesOnly
  8066. ) {
  8067. axis.softThreshold = false;
  8068. }
  8069. }
  8070. }
  8071. });
  8072. },
  8073. /**
  8074. * Translate from axis value to pixel position on the chart, or back
  8075. *
  8076. */
  8077. translate: function(val, backwards, cvsCoord, old, handleLog, pointPlacement) {
  8078. var axis = this.linkedParent || this, // #1417
  8079. sign = 1,
  8080. cvsOffset = 0,
  8081. localA = old ? axis.oldTransA : axis.transA,
  8082. localMin = old ? axis.oldMin : axis.min,
  8083. returnValue,
  8084. minPixelPadding = axis.minPixelPadding,
  8085. doPostTranslate = (axis.isOrdinal || axis.isBroken || (axis.isLog && handleLog)) && axis.lin2val;
  8086. if (!localA) {
  8087. localA = axis.transA;
  8088. }
  8089. // In vertical axes, the canvas coordinates start from 0 at the top like in
  8090. // SVG.
  8091. if (cvsCoord) {
  8092. sign *= -1; // canvas coordinates inverts the value
  8093. cvsOffset = axis.len;
  8094. }
  8095. // Handle reversed axis
  8096. if (axis.reversed) {
  8097. sign *= -1;
  8098. cvsOffset -= sign * (axis.sector || axis.len);
  8099. }
  8100. // From pixels to value
  8101. if (backwards) { // reverse translation
  8102. val = val * sign + cvsOffset;
  8103. val -= minPixelPadding;
  8104. returnValue = val / localA + localMin; // from chart pixel to value
  8105. if (doPostTranslate) { // log and ordinal axes
  8106. returnValue = axis.lin2val(returnValue);
  8107. }
  8108. // From value to pixels
  8109. } else {
  8110. if (doPostTranslate) { // log and ordinal axes
  8111. val = axis.val2lin(val);
  8112. }
  8113. returnValue = sign * (val - localMin) * localA + cvsOffset +
  8114. (sign * minPixelPadding) +
  8115. (isNumber(pointPlacement) ? localA * pointPlacement : 0);
  8116. }
  8117. return returnValue;
  8118. },
  8119. /**
  8120. * Translate a value in terms of axis units into pixels within the chart.
  8121. *
  8122. * @param {Number} value
  8123. * A value in terms of axis units.
  8124. * @param {Boolean} paneCoordinates
  8125. * Whether to return the pixel coordinate relative to the chart or
  8126. * just the axis/pane itself.
  8127. * @return {Number} Pixel position of the value on the chart or axis.
  8128. */
  8129. toPixels: function(value, paneCoordinates) {
  8130. return this.translate(value, false, !this.horiz, null, true) +
  8131. (paneCoordinates ? 0 : this.pos);
  8132. },
  8133. /**
  8134. * Translate a pixel position along the axis to a value in terms of axis
  8135. * units.
  8136. * @param {Number} pixel
  8137. * The pixel value coordinate.
  8138. * @param {Boolean} paneCoordiantes
  8139. * Whether the input pixel is relative to the chart or just the
  8140. * axis/pane itself.
  8141. * @return {Number} The axis value.
  8142. */
  8143. toValue: function(pixel, paneCoordinates) {
  8144. return this.translate(
  8145. pixel - (paneCoordinates ? 0 : this.pos),
  8146. true, !this.horiz,
  8147. null,
  8148. true
  8149. );
  8150. },
  8151. /**
  8152. * Create the path for a plot line that goes from the given value on
  8153. * this axis, across the plot to the opposite side
  8154. * @param {Number} value
  8155. * @param {Number} lineWidth Used for calculation crisp line
  8156. * @param {Number] old Use old coordinates (for resizing and rescaling)
  8157. */
  8158. getPlotLinePath: function(value, lineWidth, old, force, translatedValue) {
  8159. var axis = this,
  8160. chart = axis.chart,
  8161. axisLeft = axis.left,
  8162. axisTop = axis.top,
  8163. x1,
  8164. y1,
  8165. x2,
  8166. y2,
  8167. cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
  8168. cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
  8169. skip,
  8170. transB = axis.transB,
  8171. /**
  8172. * Check if x is between a and b. If not, either move to a/b or skip,
  8173. * depending on the force parameter.
  8174. */
  8175. between = function(x, a, b) {
  8176. if (x < a || x > b) {
  8177. if (force) {
  8178. x = Math.min(Math.max(a, x), b);
  8179. } else {
  8180. skip = true;
  8181. }
  8182. }
  8183. return x;
  8184. };
  8185. translatedValue = pick(translatedValue, axis.translate(value, null, null, old));
  8186. x1 = x2 = Math.round(translatedValue + transB);
  8187. y1 = y2 = Math.round(cHeight - translatedValue - transB);
  8188. if (!isNumber(translatedValue)) { // no min or max
  8189. skip = true;
  8190. } else if (axis.horiz) {
  8191. y1 = axisTop;
  8192. y2 = cHeight - axis.bottom;
  8193. x1 = x2 = between(x1, axisLeft, axisLeft + axis.width);
  8194. } else {
  8195. x1 = axisLeft;
  8196. x2 = cWidth - axis.right;
  8197. y1 = y2 = between(y1, axisTop, axisTop + axis.height);
  8198. }
  8199. return skip && !force ?
  8200. null :
  8201. chart.renderer.crispLine(['M', x1, y1, 'L', x2, y2], lineWidth || 1);
  8202. },
  8203. /**
  8204. * Internal function to et the tick positions of a linear axis to round
  8205. * values like whole tens or every five.
  8206. *
  8207. * @param {Number} tickInterval
  8208. * The normalized tick interval
  8209. * @param {Number} min
  8210. * Axis minimum.
  8211. * @param {Number} max
  8212. * Axis maximum.
  8213. *
  8214. * @return {Array.<Number>}
  8215. * An array of numbers where ticks should be placed.
  8216. */
  8217. getLinearTickPositions: function(tickInterval, min, max) {
  8218. var pos,
  8219. lastPos,
  8220. roundedMin = correctFloat(Math.floor(min / tickInterval) * tickInterval),
  8221. roundedMax = correctFloat(Math.ceil(max / tickInterval) * tickInterval),
  8222. tickPositions = [];
  8223. // For single points, add a tick regardless of the relative position
  8224. // (#2662, #6274)
  8225. if (this.single) {
  8226. return [min];
  8227. }
  8228. // Populate the intermediate values
  8229. pos = roundedMin;
  8230. while (pos <= roundedMax) {
  8231. // Place the tick on the rounded value
  8232. tickPositions.push(pos);
  8233. // Always add the raw tickInterval, not the corrected one.
  8234. pos = correctFloat(pos + tickInterval);
  8235. // If the interval is not big enough in the current min - max range to actually increase
  8236. // the loop variable, we need to break out to prevent endless loop. Issue #619
  8237. if (pos === lastPos) {
  8238. break;
  8239. }
  8240. // Record the last value
  8241. lastPos = pos;
  8242. }
  8243. return tickPositions;
  8244. },
  8245. /**
  8246. * Return the minor tick positions. For logarithmic axes, reuse the same logic
  8247. * as for major ticks.
  8248. */
  8249. getMinorTickPositions: function() {
  8250. var axis = this,
  8251. options = axis.options,
  8252. tickPositions = axis.tickPositions,
  8253. minorTickInterval = axis.minorTickInterval,
  8254. minorTickPositions = [],
  8255. pos,
  8256. pointRangePadding = axis.pointRangePadding || 0,
  8257. min = axis.min - pointRangePadding, // #1498
  8258. max = axis.max + pointRangePadding, // #1498
  8259. range = max - min;
  8260. // If minor ticks get too dense, they are hard to read, and may cause long running script. So we don't draw them.
  8261. if (range && range / minorTickInterval < axis.len / 3) { // #3875
  8262. if (axis.isLog) {
  8263. // For each interval in the major ticks, compute the minor ticks
  8264. // separately.
  8265. each(this.paddedTicks, function(pos, i, paddedTicks) {
  8266. if (i) {
  8267. minorTickPositions.push.apply(
  8268. minorTickPositions,
  8269. axis.getLogTickPositions(
  8270. minorTickInterval,
  8271. paddedTicks[i - 1],
  8272. paddedTicks[i],
  8273. true
  8274. )
  8275. );
  8276. }
  8277. });
  8278. } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314
  8279. minorTickPositions = minorTickPositions.concat(
  8280. axis.getTimeTicks(
  8281. axis.normalizeTimeTickInterval(minorTickInterval),
  8282. min,
  8283. max,
  8284. options.startOfWeek
  8285. )
  8286. );
  8287. } else {
  8288. for (
  8289. pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval
  8290. ) {
  8291. // Very, very, tight grid lines (#5771)
  8292. if (pos === minorTickPositions[0]) {
  8293. break;
  8294. }
  8295. minorTickPositions.push(pos);
  8296. }
  8297. }
  8298. }
  8299. if (minorTickPositions.length !== 0) {
  8300. axis.trimTicks(minorTickPositions); // #3652 #3743 #1498 #6330
  8301. }
  8302. return minorTickPositions;
  8303. },
  8304. /**
  8305. * Adjust the min and max for the minimum range. Keep in mind that the series data is
  8306. * not yet processed, so we don't have information on data cropping and grouping, or
  8307. * updated axis.pointRange or series.pointRange. The data can't be processed until
  8308. * we have finally established min and max.
  8309. *
  8310. * @private
  8311. */
  8312. adjustForMinRange: function() {
  8313. var axis = this,
  8314. options = axis.options,
  8315. min = axis.min,
  8316. max = axis.max,
  8317. zoomOffset,
  8318. spaceAvailable,
  8319. closestDataRange,
  8320. i,
  8321. distance,
  8322. xData,
  8323. loopLength,
  8324. minArgs,
  8325. maxArgs,
  8326. minRange;
  8327. // Set the automatic minimum range based on the closest point distance
  8328. if (axis.isXAxis && axis.minRange === undefined && !axis.isLog) {
  8329. if (defined(options.min) || defined(options.max)) {
  8330. axis.minRange = null; // don't do this again
  8331. } else {
  8332. // Find the closest distance between raw data points, as opposed to
  8333. // closestPointRange that applies to processed points (cropped and grouped)
  8334. each(axis.series, function(series) {
  8335. xData = series.xData;
  8336. loopLength = series.xIncrement ? 1 : xData.length - 1;
  8337. for (i = loopLength; i > 0; i--) {
  8338. distance = xData[i] - xData[i - 1];
  8339. if (closestDataRange === undefined || distance < closestDataRange) {
  8340. closestDataRange = distance;
  8341. }
  8342. }
  8343. });
  8344. axis.minRange = Math.min(closestDataRange * 5, axis.dataMax - axis.dataMin);
  8345. }
  8346. }
  8347. // if minRange is exceeded, adjust
  8348. if (max - min < axis.minRange) {
  8349. spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange;
  8350. minRange = axis.minRange;
  8351. zoomOffset = (minRange - max + min) / 2;
  8352. // if min and max options have been set, don't go beyond it
  8353. minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
  8354. if (spaceAvailable) { // if space is available, stay within the data range
  8355. minArgs[2] = axis.isLog ? axis.log2lin(axis.dataMin) : axis.dataMin;
  8356. }
  8357. min = arrayMax(minArgs);
  8358. maxArgs = [min + minRange, pick(options.max, min + minRange)];
  8359. if (spaceAvailable) { // if space is availabe, stay within the data range
  8360. maxArgs[2] = axis.isLog ? axis.log2lin(axis.dataMax) : axis.dataMax;
  8361. }
  8362. max = arrayMin(maxArgs);
  8363. // now if the max is adjusted, adjust the min back
  8364. if (max - min < minRange) {
  8365. minArgs[0] = max - minRange;
  8366. minArgs[1] = pick(options.min, max - minRange);
  8367. min = arrayMax(minArgs);
  8368. }
  8369. }
  8370. // Record modified extremes
  8371. axis.min = min;
  8372. axis.max = max;
  8373. },
  8374. /**
  8375. * Find the closestPointRange across all series.
  8376. *
  8377. * @private
  8378. */
  8379. getClosest: function() {
  8380. var ret;
  8381. if (this.categories) {
  8382. ret = 1;
  8383. } else {
  8384. each(this.series, function(series) {
  8385. var seriesClosest = series.closestPointRange,
  8386. visible = series.visible ||
  8387. !series.chart.options.chart.ignoreHiddenSeries;
  8388. if (!series.noSharedTooltip &&
  8389. defined(seriesClosest) &&
  8390. visible
  8391. ) {
  8392. ret = defined(ret) ?
  8393. Math.min(ret, seriesClosest) :
  8394. seriesClosest;
  8395. }
  8396. });
  8397. }
  8398. return ret;
  8399. },
  8400. /**
  8401. * When a point name is given and no x, search for the name in the existing categories,
  8402. * or if categories aren't provided, search names or create a new category (#2522).
  8403. */
  8404. nameToX: function(point) {
  8405. var explicitCategories = isArray(this.categories),
  8406. names = explicitCategories ? this.categories : this.names,
  8407. nameX = point.options.x,
  8408. x;
  8409. point.series.requireSorting = false;
  8410. if (!defined(nameX)) {
  8411. nameX = this.options.uniqueNames === false ?
  8412. point.series.autoIncrement() :
  8413. inArray(point.name, names);
  8414. }
  8415. if (nameX === -1) { // The name is not found in currenct categories
  8416. if (!explicitCategories) {
  8417. x = names.length;
  8418. }
  8419. } else {
  8420. x = nameX;
  8421. }
  8422. // Write the last point's name to the names array
  8423. if (x !== undefined) {
  8424. this.names[x] = point.name;
  8425. }
  8426. return x;
  8427. },
  8428. /**
  8429. * When changes have been done to series data, update the axis.names.
  8430. */
  8431. updateNames: function() {
  8432. var axis = this;
  8433. if (this.names.length > 0) {
  8434. this.names.length = 0;
  8435. this.minRange = this.userMinRange; // Reset
  8436. each(this.series || [], function(series) {
  8437. // Reset incrementer (#5928)
  8438. series.xIncrement = null;
  8439. // When adding a series, points are not yet generated
  8440. if (!series.points || series.isDirtyData) {
  8441. series.processData();
  8442. series.generatePoints();
  8443. }
  8444. each(series.points, function(point, i) {
  8445. var x;
  8446. if (point.options) {
  8447. x = axis.nameToX(point);
  8448. if (x !== undefined && x !== point.x) {
  8449. point.x = x;
  8450. series.xData[i] = x;
  8451. }
  8452. }
  8453. });
  8454. });
  8455. }
  8456. },
  8457. /**
  8458. * Update translation information
  8459. */
  8460. setAxisTranslation: function(saveOld) {
  8461. var axis = this,
  8462. range = axis.max - axis.min,
  8463. pointRange = axis.axisPointRange || 0,
  8464. closestPointRange,
  8465. minPointOffset = 0,
  8466. pointRangePadding = 0,
  8467. linkedParent = axis.linkedParent,
  8468. ordinalCorrection,
  8469. hasCategories = !!axis.categories,
  8470. transA = axis.transA,
  8471. isXAxis = axis.isXAxis;
  8472. // Adjust translation for padding. Y axis with categories need to go through the same (#1784).
  8473. if (isXAxis || hasCategories || pointRange) {
  8474. // Get the closest points
  8475. closestPointRange = axis.getClosest();
  8476. if (linkedParent) {
  8477. minPointOffset = linkedParent.minPointOffset;
  8478. pointRangePadding = linkedParent.pointRangePadding;
  8479. } else {
  8480. each(axis.series, function(series) {
  8481. var seriesPointRange = hasCategories ?
  8482. 1 :
  8483. (isXAxis ?
  8484. pick(series.options.pointRange, closestPointRange, 0) :
  8485. (axis.axisPointRange || 0)), // #2806
  8486. pointPlacement = series.options.pointPlacement;
  8487. pointRange = Math.max(pointRange, seriesPointRange);
  8488. if (!axis.single) {
  8489. // minPointOffset is the value padding to the left of the axis in order to make
  8490. // room for points with a pointRange, typically columns. When the pointPlacement option
  8491. // is 'between' or 'on', this padding does not apply.
  8492. minPointOffset = Math.max(
  8493. minPointOffset,
  8494. isString(pointPlacement) ? 0 : seriesPointRange / 2
  8495. );
  8496. // Determine the total padding needed to the length of the axis to make room for the
  8497. // pointRange. If the series' pointPlacement is 'on', no padding is added.
  8498. pointRangePadding = Math.max(
  8499. pointRangePadding,
  8500. pointPlacement === 'on' ? 0 : seriesPointRange
  8501. );
  8502. }
  8503. });
  8504. }
  8505. // Record minPointOffset and pointRangePadding
  8506. ordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853
  8507. axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection;
  8508. axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection;
  8509. // pointRange means the width reserved for each point, like in a column chart
  8510. axis.pointRange = Math.min(pointRange, range);
  8511. // closestPointRange means the closest distance between points. In columns
  8512. // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange
  8513. // is some other value
  8514. if (isXAxis) {
  8515. axis.closestPointRange = closestPointRange;
  8516. }
  8517. }
  8518. // Secondary values
  8519. if (saveOld) {
  8520. axis.oldTransA = transA;
  8521. }
  8522. axis.translationSlope = axis.transA = transA =
  8523. axis.options.staticScale ||
  8524. axis.len / ((range + pointRangePadding) || 1);
  8525. axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend
  8526. axis.minPixelPadding = transA * minPointOffset;
  8527. },
  8528. minFromRange: function() {
  8529. return this.max - this.range;
  8530. },
  8531. /**
  8532. * Set the tick positions to round values and optionally extend the extremes
  8533. * to the nearest tick
  8534. */
  8535. setTickInterval: function(secondPass) {
  8536. var axis = this,
  8537. chart = axis.chart,
  8538. options = axis.options,
  8539. isLog = axis.isLog,
  8540. log2lin = axis.log2lin,
  8541. isDatetimeAxis = axis.isDatetimeAxis,
  8542. isXAxis = axis.isXAxis,
  8543. isLinked = axis.isLinked,
  8544. maxPadding = options.maxPadding,
  8545. minPadding = options.minPadding,
  8546. length,
  8547. linkedParentExtremes,
  8548. tickIntervalOption = options.tickInterval,
  8549. minTickInterval,
  8550. tickPixelIntervalOption = options.tickPixelInterval,
  8551. categories = axis.categories,
  8552. threshold = axis.threshold,
  8553. softThreshold = axis.softThreshold,
  8554. thresholdMin,
  8555. thresholdMax,
  8556. hardMin,
  8557. hardMax;
  8558. if (!isDatetimeAxis && !categories && !isLinked) {
  8559. this.getTickAmount();
  8560. }
  8561. // Min or max set either by zooming/setExtremes or initial options
  8562. hardMin = pick(axis.userMin, options.min);
  8563. hardMax = pick(axis.userMax, options.max);
  8564. // Linked axis gets the extremes from the parent axis
  8565. if (isLinked) {
  8566. axis.linkedParent = chart[axis.coll][options.linkedTo];
  8567. linkedParentExtremes = axis.linkedParent.getExtremes();
  8568. axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
  8569. axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
  8570. if (options.type !== axis.linkedParent.options.type) {
  8571. H.error(11, 1); // Can't link axes of different type
  8572. }
  8573. // Initial min and max from the extreme data values
  8574. } else {
  8575. // Adjust to hard threshold
  8576. if (!softThreshold && defined(threshold)) {
  8577. if (axis.dataMin >= threshold) {
  8578. thresholdMin = threshold;
  8579. minPadding = 0;
  8580. } else if (axis.dataMax <= threshold) {
  8581. thresholdMax = threshold;
  8582. maxPadding = 0;
  8583. }
  8584. }
  8585. axis.min = pick(hardMin, thresholdMin, axis.dataMin);
  8586. axis.max = pick(hardMax, thresholdMax, axis.dataMax);
  8587. }
  8588. if (isLog) {
  8589. if (
  8590. axis.positiveValuesOnly &&
  8591. !secondPass &&
  8592. Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0
  8593. ) { // #978
  8594. H.error(10, 1); // Can't plot negative values on log axis
  8595. }
  8596. // The correctFloat cures #934, float errors on full tens. But it
  8597. // was too aggressive for #4360 because of conversion back to lin,
  8598. // therefore use precision 15.
  8599. axis.min = correctFloat(log2lin(axis.min), 15);
  8600. axis.max = correctFloat(log2lin(axis.max), 15);
  8601. }
  8602. // handle zoomed range
  8603. if (axis.range && defined(axis.max)) {
  8604. axis.userMin = axis.min = hardMin = Math.max(axis.min, axis.minFromRange()); // #618
  8605. axis.userMax = hardMax = axis.max;
  8606. axis.range = null; // don't use it when running setExtremes
  8607. }
  8608. // Hook for Highstock Scroller. Consider combining with beforePadding.
  8609. fireEvent(axis, 'foundExtremes');
  8610. // Hook for adjusting this.min and this.max. Used by bubble series.
  8611. if (axis.beforePadding) {
  8612. axis.beforePadding();
  8613. }
  8614. // adjust min and max for the minimum range
  8615. axis.adjustForMinRange();
  8616. // Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding
  8617. // into account, we do this after computing tick interval (#1337).
  8618. if (!categories && !axis.axisPointRange && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {
  8619. length = axis.max - axis.min;
  8620. if (length) {
  8621. if (!defined(hardMin) && minPadding) {
  8622. axis.min -= length * minPadding;
  8623. }
  8624. if (!defined(hardMax) && maxPadding) {
  8625. axis.max += length * maxPadding;
  8626. }
  8627. }
  8628. }
  8629. // Handle options for floor, ceiling, softMin and softMax (#6359)
  8630. if (isNumber(options.softMin)) {
  8631. axis.min = Math.min(axis.min, options.softMin);
  8632. }
  8633. if (isNumber(options.softMax)) {
  8634. axis.max = Math.max(axis.max, options.softMax);
  8635. }
  8636. if (isNumber(options.floor)) {
  8637. axis.min = Math.max(axis.min, options.floor);
  8638. }
  8639. if (isNumber(options.ceiling)) {
  8640. axis.max = Math.min(axis.max, options.ceiling);
  8641. }
  8642. // When the threshold is soft, adjust the extreme value only if
  8643. // the data extreme and the padded extreme land on either side of the threshold. For example,
  8644. // a series of [0, 1, 2, 3] would make the yAxis add a tick for -1 because of the
  8645. // default minPadding and startOnTick options. This is prevented by the softThreshold
  8646. // option.
  8647. if (softThreshold && defined(axis.dataMin)) {
  8648. threshold = threshold || 0;
  8649. if (!defined(hardMin) && axis.min < threshold && axis.dataMin >= threshold) {
  8650. axis.min = threshold;
  8651. } else if (!defined(hardMax) && axis.max > threshold && axis.dataMax <= threshold) {
  8652. axis.max = threshold;
  8653. }
  8654. }
  8655. // get tickInterval
  8656. if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {
  8657. axis.tickInterval = 1;
  8658. } else if (isLinked && !tickIntervalOption &&
  8659. tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) {
  8660. axis.tickInterval = tickIntervalOption = axis.linkedParent.tickInterval;
  8661. } else {
  8662. axis.tickInterval = pick(
  8663. tickIntervalOption,
  8664. this.tickAmount ? ((axis.max - axis.min) / Math.max(this.tickAmount - 1, 1)) : undefined,
  8665. categories ? // for categoried axis, 1 is default, for linear axis use tickPix
  8666. 1 :
  8667. // don't let it be more than the data range
  8668. (axis.max - axis.min) * tickPixelIntervalOption / Math.max(axis.len, tickPixelIntervalOption)
  8669. );
  8670. }
  8671. // Now we're finished detecting min and max, crop and group series data. This
  8672. // is in turn needed in order to find tick positions in ordinal axes.
  8673. if (isXAxis && !secondPass) {
  8674. each(axis.series, function(series) {
  8675. series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax);
  8676. });
  8677. }
  8678. // set the translation factor used in translate function
  8679. axis.setAxisTranslation(true);
  8680. // hook for ordinal axes and radial axes
  8681. if (axis.beforeSetTickPositions) {
  8682. axis.beforeSetTickPositions();
  8683. }
  8684. // hook for extensions, used in Highstock ordinal axes
  8685. if (axis.postProcessTickInterval) {
  8686. axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
  8687. }
  8688. // In column-like charts, don't cramp in more ticks than there are points (#1943, #4184)
  8689. if (axis.pointRange && !tickIntervalOption) {
  8690. axis.tickInterval = Math.max(axis.pointRange, axis.tickInterval);
  8691. }
  8692. // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined.
  8693. minTickInterval = pick(options.minTickInterval, axis.isDatetimeAxis && axis.closestPointRange);
  8694. if (!tickIntervalOption && axis.tickInterval < minTickInterval) {
  8695. axis.tickInterval = minTickInterval;
  8696. }
  8697. // for linear axes, get magnitude and normalize the interval
  8698. if (!isDatetimeAxis && !isLog && !tickIntervalOption) {
  8699. axis.tickInterval = normalizeTickInterval(
  8700. axis.tickInterval,
  8701. null,
  8702. getMagnitude(axis.tickInterval),
  8703. // If the tick interval is between 0.5 and 5 and the axis max is in the order of
  8704. // thousands, chances are we are dealing with years. Don't allow decimals. #3363.
  8705. pick(options.allowDecimals, !(axis.tickInterval > 0.5 && axis.tickInterval < 5 && axis.max > 1000 && axis.max < 9999)), !!this.tickAmount
  8706. );
  8707. }
  8708. // Prevent ticks from getting so close that we can't draw the labels
  8709. if (!this.tickAmount) {
  8710. axis.tickInterval = axis.unsquish();
  8711. }
  8712. this.setTickPositions();
  8713. },
  8714. /**
  8715. * Now we have computed the normalized tickInterval, get the tick positions
  8716. */
  8717. setTickPositions: function() {
  8718. var options = this.options,
  8719. tickPositions,
  8720. tickPositionsOption = options.tickPositions,
  8721. tickPositioner = options.tickPositioner,
  8722. startOnTick = options.startOnTick,
  8723. endOnTick = options.endOnTick;
  8724. // Set the tickmarkOffset
  8725. this.tickmarkOffset = (this.categories && options.tickmarkPlacement === 'between' &&
  8726. this.tickInterval === 1) ? 0.5 : 0; // #3202
  8727. // get minorTickInterval
  8728. this.minorTickInterval = options.minorTickInterval === 'auto' && this.tickInterval ?
  8729. this.tickInterval / 5 : options.minorTickInterval;
  8730. // When there is only one point, or all points have the same value on
  8731. // this axis, then min and max are equal and tickPositions.length is 0
  8732. // or 1. In this case, add some padding in order to center the point,
  8733. // but leave it with one tick. #1337.
  8734. this.single =
  8735. this.min === this.max &&
  8736. defined(this.min) &&
  8737. !this.tickAmount &&
  8738. (
  8739. // Data is on integer (#6563)
  8740. parseInt(this.min, 10) === this.min ||
  8741. // Between integers and decimals are not allowed (#6274)
  8742. options.allowDecimals !== false
  8743. );
  8744. // Find the tick positions
  8745. this.tickPositions = tickPositions = tickPositionsOption && tickPositionsOption.slice(); // Work on a copy (#1565)
  8746. if (!tickPositions) {
  8747. if (this.isDatetimeAxis) {
  8748. tickPositions = this.getTimeTicks(
  8749. this.normalizeTimeTickInterval(
  8750. this.tickInterval,
  8751. options.units
  8752. ),
  8753. this.min,
  8754. this.max,
  8755. options.startOfWeek,
  8756. this.ordinalPositions,
  8757. this.closestPointRange,
  8758. true
  8759. );
  8760. } else if (this.isLog) {
  8761. tickPositions = this.getLogTickPositions(
  8762. this.tickInterval,
  8763. this.min,
  8764. this.max
  8765. );
  8766. } else {
  8767. tickPositions = this.getLinearTickPositions(
  8768. this.tickInterval,
  8769. this.min,
  8770. this.max
  8771. );
  8772. }
  8773. // Too dense ticks, keep only the first and last (#4477)
  8774. if (tickPositions.length > this.len) {
  8775. tickPositions = [tickPositions[0], tickPositions.pop()];
  8776. }
  8777. this.tickPositions = tickPositions;
  8778. // Run the tick positioner callback, that allows modifying auto tick positions.
  8779. if (tickPositioner) {
  8780. tickPositioner = tickPositioner.apply(this, [this.min, this.max]);
  8781. if (tickPositioner) {
  8782. this.tickPositions = tickPositions = tickPositioner;
  8783. }
  8784. }
  8785. }
  8786. // Reset min/max or remove extremes based on start/end on tick
  8787. this.paddedTicks = tickPositions.slice(0); // Used for logarithmic minor
  8788. this.trimTicks(tickPositions, startOnTick, endOnTick);
  8789. if (!this.isLinked) {
  8790. // Substract half a unit (#2619, #2846, #2515, #3390)
  8791. if (this.single) {
  8792. this.min -= 0.5;
  8793. this.max += 0.5;
  8794. }
  8795. if (!tickPositionsOption && !tickPositioner) {
  8796. this.adjustTickAmount();
  8797. }
  8798. }
  8799. },
  8800. /**
  8801. * Handle startOnTick and endOnTick by either adapting to padding min/max or rounded min/max
  8802. */
  8803. trimTicks: function(tickPositions, startOnTick, endOnTick) {
  8804. var roundedMin = tickPositions[0],
  8805. roundedMax = tickPositions[tickPositions.length - 1],
  8806. minPointOffset = this.minPointOffset || 0;
  8807. if (!this.isLinked) {
  8808. if (startOnTick && roundedMin !== -Infinity) { // #6502
  8809. this.min = roundedMin;
  8810. } else {
  8811. while (this.min - minPointOffset > tickPositions[0]) {
  8812. tickPositions.shift();
  8813. }
  8814. }
  8815. if (endOnTick) {
  8816. this.max = roundedMax;
  8817. } else {
  8818. while (this.max + minPointOffset < tickPositions[tickPositions.length - 1]) {
  8819. tickPositions.pop();
  8820. }
  8821. }
  8822. // If no tick are left, set one tick in the middle (#3195)
  8823. if (tickPositions.length === 0 && defined(roundedMin)) {
  8824. tickPositions.push((roundedMax + roundedMin) / 2);
  8825. }
  8826. }
  8827. },
  8828. /**
  8829. * Check if there are multiple axes in the same pane.
  8830. *
  8831. * @private
  8832. * @return {Boolean}
  8833. * True if there are other axes.
  8834. */
  8835. alignToOthers: function() {
  8836. var others = {}, // Whether there is another axis to pair with this one
  8837. hasOther,
  8838. options = this.options;
  8839. if (
  8840. // Only if alignTicks is true
  8841. this.chart.options.chart.alignTicks !== false &&
  8842. options.alignTicks !== false &&
  8843. // Don't try to align ticks on a log axis, they are not evenly
  8844. // spaced (#6021)
  8845. !this.isLog
  8846. ) {
  8847. each(this.chart[this.coll], function(axis) {
  8848. var otherOptions = axis.options,
  8849. horiz = axis.horiz,
  8850. key = [
  8851. horiz ? otherOptions.left : otherOptions.top,
  8852. otherOptions.width,
  8853. otherOptions.height,
  8854. otherOptions.pane
  8855. ].join(',');
  8856. if (axis.series.length) { // #4442
  8857. if (others[key]) {
  8858. hasOther = true; // #4201
  8859. } else {
  8860. others[key] = 1;
  8861. }
  8862. }
  8863. });
  8864. }
  8865. return hasOther;
  8866. },
  8867. /**
  8868. * Set the max ticks of either the x and y axis collection
  8869. */
  8870. getTickAmount: function() {
  8871. var options = this.options,
  8872. tickAmount = options.tickAmount,
  8873. tickPixelInterval = options.tickPixelInterval;
  8874. if (!defined(options.tickInterval) && this.len < tickPixelInterval && !this.isRadial &&
  8875. !this.isLog && options.startOnTick && options.endOnTick) {
  8876. tickAmount = 2;
  8877. }
  8878. if (!tickAmount && this.alignToOthers()) {
  8879. // Add 1 because 4 tick intervals require 5 ticks (including first and last)
  8880. tickAmount = Math.ceil(this.len / tickPixelInterval) + 1;
  8881. }
  8882. // For tick amounts of 2 and 3, compute five ticks and remove the intermediate ones. This
  8883. // prevents the axis from adding ticks that are too far away from the data extremes.
  8884. if (tickAmount < 4) {
  8885. this.finalTickAmt = tickAmount;
  8886. tickAmount = 5;
  8887. }
  8888. this.tickAmount = tickAmount;
  8889. },
  8890. /**
  8891. * When using multiple axes, adjust the number of ticks to match the highest
  8892. * number of ticks in that group.
  8893. *
  8894. * @private
  8895. */
  8896. adjustTickAmount: function() {
  8897. var tickInterval = this.tickInterval,
  8898. tickPositions = this.tickPositions,
  8899. tickAmount = this.tickAmount,
  8900. finalTickAmt = this.finalTickAmt,
  8901. currentTickAmount = tickPositions && tickPositions.length,
  8902. i,
  8903. len;
  8904. if (currentTickAmount < tickAmount) {
  8905. while (tickPositions.length < tickAmount) {
  8906. tickPositions.push(correctFloat(
  8907. tickPositions[tickPositions.length - 1] + tickInterval
  8908. ));
  8909. }
  8910. this.transA *= (currentTickAmount - 1) / (tickAmount - 1);
  8911. this.max = tickPositions[tickPositions.length - 1];
  8912. // We have too many ticks, run second pass to try to reduce ticks
  8913. } else if (currentTickAmount > tickAmount) {
  8914. this.tickInterval *= 2;
  8915. this.setTickPositions();
  8916. }
  8917. // The finalTickAmt property is set in getTickAmount
  8918. if (defined(finalTickAmt)) {
  8919. i = len = tickPositions.length;
  8920. while (i--) {
  8921. if (
  8922. (finalTickAmt === 3 && i % 2 === 1) || // Remove every other tick
  8923. (finalTickAmt <= 2 && i > 0 && i < len - 1) // Remove all but first and last
  8924. ) {
  8925. tickPositions.splice(i, 1);
  8926. }
  8927. }
  8928. this.finalTickAmt = undefined;
  8929. }
  8930. },
  8931. /**
  8932. * Set the scale based on data min and max, user set min and max or options
  8933. *
  8934. */
  8935. setScale: function() {
  8936. var axis = this,
  8937. isDirtyData,
  8938. isDirtyAxisLength;
  8939. axis.oldMin = axis.min;
  8940. axis.oldMax = axis.max;
  8941. axis.oldAxisLength = axis.len;
  8942. // set the new axisLength
  8943. axis.setAxisSize();
  8944. //axisLength = horiz ? axisWidth : axisHeight;
  8945. isDirtyAxisLength = axis.len !== axis.oldAxisLength;
  8946. // is there new data?
  8947. each(axis.series, function(series) {
  8948. if (series.isDirtyData || series.isDirty ||
  8949. series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well
  8950. isDirtyData = true;
  8951. }
  8952. });
  8953. // do we really need to go through all this?
  8954. if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw ||
  8955. axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax || axis.alignToOthers()) {
  8956. if (axis.resetStacks) {
  8957. axis.resetStacks();
  8958. }
  8959. axis.forceRedraw = false;
  8960. // get data extremes if needed
  8961. axis.getSeriesExtremes();
  8962. // get fixed positions based on tickInterval
  8963. axis.setTickInterval();
  8964. // record old values to decide whether a rescale is necessary later on (#540)
  8965. axis.oldUserMin = axis.userMin;
  8966. axis.oldUserMax = axis.userMax;
  8967. // Mark as dirty if it is not already set to dirty and extremes have changed. #595.
  8968. if (!axis.isDirty) {
  8969. axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax;
  8970. }
  8971. } else if (axis.cleanStacks) {
  8972. axis.cleanStacks();
  8973. }
  8974. },
  8975. /**
  8976. * Set the minimum and maximum of the axes after render time. If the
  8977. * `startOnTick` and `endOnTick` options are true, the minimum and maximum
  8978. * values are rounded off to the nearest tick. To prevent this, these
  8979. * options can be set to false before calling setExtremes. Also, setExtremes
  8980. * will not allow a range lower than the `minRange` option, which by default
  8981. * is the range of five points.
  8982. *
  8983. * @param {Number} [newMin]
  8984. * The new minimum value.
  8985. * @param {Number} [newMax]
  8986. * The new maximum value.
  8987. * @param {Boolean} [redraw=true]
  8988. * Whether to redraw the chart or wait for an explicit call to
  8989. * {@link Highcharts.Chart#redraw}
  8990. * @param {AnimationOptions} [animation=true]
  8991. * Enable or modify animations.
  8992. * @param {Object} [eventArguments]
  8993. * Arguments to be accessed in event handler.
  8994. *
  8995. * @sample highcharts/members/axis-setextremes/
  8996. * Set extremes from a button
  8997. * @sample highcharts/members/axis-setextremes-datetime/
  8998. * Set extremes on a datetime axis
  8999. * @sample highcharts/members/axis-setextremes-off-ticks/
  9000. * Set extremes off ticks
  9001. * @sample stock/members/axis-setextremes/
  9002. * Set extremes in Highstock
  9003. * @sample maps/members/axis-setextremes/
  9004. * Set extremes in Highmaps
  9005. */
  9006. setExtremes: function(newMin, newMax, redraw, animation, eventArguments) {
  9007. var axis = this,
  9008. chart = axis.chart;
  9009. redraw = pick(redraw, true); // defaults to true
  9010. each(axis.series, function(serie) {
  9011. delete serie.kdTree;
  9012. });
  9013. // Extend the arguments with min and max
  9014. eventArguments = extend(eventArguments, {
  9015. min: newMin,
  9016. max: newMax
  9017. });
  9018. // Fire the event
  9019. fireEvent(axis, 'setExtremes', eventArguments, function() { // the default event handler
  9020. axis.userMin = newMin;
  9021. axis.userMax = newMax;
  9022. axis.eventArgs = eventArguments;
  9023. if (redraw) {
  9024. chart.redraw(animation);
  9025. }
  9026. });
  9027. },
  9028. /**
  9029. * Overridable method for zooming chart. Pulled out in a separate method to allow overriding
  9030. * in stock charts.
  9031. */
  9032. zoom: function(newMin, newMax) {
  9033. var dataMin = this.dataMin,
  9034. dataMax = this.dataMax,
  9035. options = this.options,
  9036. min = Math.min(dataMin, pick(options.min, dataMin)),
  9037. max = Math.max(dataMax, pick(options.max, dataMax));
  9038. if (newMin !== this.min || newMax !== this.max) { // #5790
  9039. // Prevent pinch zooming out of range. Check for defined is for #1946. #1734.
  9040. if (!this.allowZoomOutside) {
  9041. // #6014, sometimes newMax will be smaller than min (or newMin will be larger than max).
  9042. if (defined(dataMin)) {
  9043. if (newMin < min) {
  9044. newMin = min;
  9045. }
  9046. if (newMin > max) {
  9047. newMin = max;
  9048. }
  9049. }
  9050. if (defined(dataMax)) {
  9051. if (newMax < min) {
  9052. newMax = min;
  9053. }
  9054. if (newMax > max) {
  9055. newMax = max;
  9056. }
  9057. }
  9058. }
  9059. // In full view, displaying the reset zoom button is not required
  9060. this.displayBtn = newMin !== undefined || newMax !== undefined;
  9061. // Do it
  9062. this.setExtremes(
  9063. newMin,
  9064. newMax,
  9065. false,
  9066. undefined, {
  9067. trigger: 'zoom'
  9068. }
  9069. );
  9070. }
  9071. return true;
  9072. },
  9073. /**
  9074. * Update the axis metrics
  9075. */
  9076. setAxisSize: function() {
  9077. var chart = this.chart,
  9078. options = this.options,
  9079. offsets = options.offsets || [0, 0, 0, 0], // top / right / bottom / left
  9080. horiz = this.horiz,
  9081. width = pick(options.width, chart.plotWidth - offsets[3] + offsets[1]),
  9082. height = pick(options.height, chart.plotHeight - offsets[0] + offsets[2]),
  9083. top = pick(options.top, chart.plotTop + offsets[0]),
  9084. left = pick(options.left, chart.plotLeft + offsets[3]),
  9085. percentRegex = /%$/;
  9086. // Check for percentage based input values. Rounding fixes problems with
  9087. // column overflow and plot line filtering (#4898, #4899)
  9088. if (percentRegex.test(height)) {
  9089. height = Math.round(parseFloat(height) / 100 * chart.plotHeight);
  9090. }
  9091. if (percentRegex.test(top)) {
  9092. top = Math.round(parseFloat(top) / 100 * chart.plotHeight + chart.plotTop);
  9093. }
  9094. // Expose basic values to use in Series object and navigator
  9095. this.left = left;
  9096. this.top = top;
  9097. this.width = width;
  9098. this.height = height;
  9099. this.bottom = chart.chartHeight - height - top;
  9100. this.right = chart.chartWidth - width - left;
  9101. // Direction agnostic properties
  9102. this.len = Math.max(horiz ? width : height, 0); // Math.max fixes #905
  9103. this.pos = horiz ? left : top; // distance from SVG origin
  9104. },
  9105. /**
  9106. * The returned object literal from the {@link Highcharts.Axis#getExtremes}
  9107. * function.
  9108. * @typedef {Object} Extremes
  9109. * @property {Number} dataMax
  9110. * The maximum value of the axis' associated series.
  9111. * @property {Number} dataMin
  9112. * The minimum value of the axis' associated series.
  9113. * @property {Number} max
  9114. * The maximum axis value, either automatic or set manually. If the
  9115. * `max` option is not set, `maxPadding` is 0 and `endOnTick` is
  9116. * false, this value will be the same as `dataMax`.
  9117. * @property {Number} min
  9118. * The minimum axis value, either automatic or set manually. If the
  9119. * `min` option is not set, `minPadding` is 0 and `startOnTick` is
  9120. * false, this value will be the same as `dataMin`.
  9121. */
  9122. /**
  9123. * Get the current extremes for the axis.
  9124. *
  9125. * @returns {Extremes}
  9126. * An object containing extremes information.
  9127. *
  9128. * @sample members/axis-getextremes/
  9129. * Report extremes by click on a button
  9130. * @sample maps/members/axis-getextremes/
  9131. * Get extremes in Highmaps
  9132. */
  9133. getExtremes: function() {
  9134. var axis = this,
  9135. isLog = axis.isLog,
  9136. lin2log = axis.lin2log;
  9137. return {
  9138. min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
  9139. max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
  9140. dataMin: axis.dataMin,
  9141. dataMax: axis.dataMax,
  9142. userMin: axis.userMin,
  9143. userMax: axis.userMax
  9144. };
  9145. },
  9146. /**
  9147. * Get the zero plane either based on zero or on the min or max value.
  9148. * Used in bar and area plots
  9149. */
  9150. getThreshold: function(threshold) {
  9151. var axis = this,
  9152. isLog = axis.isLog,
  9153. lin2log = axis.lin2log,
  9154. realMin = isLog ? lin2log(axis.min) : axis.min,
  9155. realMax = isLog ? lin2log(axis.max) : axis.max;
  9156. if (threshold === null) {
  9157. threshold = realMin;
  9158. } else if (realMin > threshold) {
  9159. threshold = realMin;
  9160. } else if (realMax < threshold) {
  9161. threshold = realMax;
  9162. }
  9163. return axis.translate(threshold, 0, 1, 0, 1);
  9164. },
  9165. /**
  9166. * Compute auto alignment for the axis label based on which side the axis is
  9167. * on and the given rotation for the label.
  9168. *
  9169. * @param {Number} rotation
  9170. * The rotation in degrees as set by either the `rotation` or
  9171. * `autoRotation` options.
  9172. * @private
  9173. */
  9174. autoLabelAlign: function(rotation) {
  9175. var ret,
  9176. angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;
  9177. if (angle > 15 && angle < 165) {
  9178. ret = 'right';
  9179. } else if (angle > 195 && angle < 345) {
  9180. ret = 'left';
  9181. } else {
  9182. ret = 'center';
  9183. }
  9184. return ret;
  9185. },
  9186. /**
  9187. * Get the tick length and width for the axis.
  9188. * @param {String} prefix 'tick' or 'minorTick'
  9189. * @returns {Array} An array of tickLength and tickWidth
  9190. */
  9191. tickSize: function(prefix) {
  9192. var options = this.options,
  9193. tickLength = options[prefix + 'Length'],
  9194. tickWidth = pick(options[prefix + 'Width'], prefix === 'tick' && this.isXAxis ? 1 : 0); // X axis defaults to 1
  9195. if (tickWidth && tickLength) {
  9196. // Negate the length
  9197. if (options[prefix + 'Position'] === 'inside') {
  9198. tickLength = -tickLength;
  9199. }
  9200. return [tickLength, tickWidth];
  9201. }
  9202. },
  9203. /**
  9204. * Return the size of the labels
  9205. */
  9206. labelMetrics: function() {
  9207. var index = this.tickPositions && this.tickPositions[0] || 0;
  9208. return this.chart.renderer.fontMetrics(
  9209. this.options.labels.style && this.options.labels.style.fontSize,
  9210. this.ticks[index] && this.ticks[index].label
  9211. );
  9212. },
  9213. /**
  9214. * Prevent the ticks from getting so close we can't draw the labels. On a horizontal
  9215. * axis, this is handled by rotating the labels, removing ticks and adding ellipsis.
  9216. * On a vertical axis remove ticks and add ellipsis.
  9217. */
  9218. unsquish: function() {
  9219. var labelOptions = this.options.labels,
  9220. horiz = this.horiz,
  9221. tickInterval = this.tickInterval,
  9222. newTickInterval = tickInterval,
  9223. slotSize = this.len / (((this.categories ? 1 : 0) + this.max - this.min) / tickInterval),
  9224. rotation,
  9225. rotationOption = labelOptions.rotation,
  9226. labelMetrics = this.labelMetrics(),
  9227. step,
  9228. bestScore = Number.MAX_VALUE,
  9229. autoRotation,
  9230. // Return the multiple of tickInterval that is needed to avoid collision
  9231. getStep = function(spaceNeeded) {
  9232. var step = spaceNeeded / (slotSize || 1);
  9233. step = step > 1 ? Math.ceil(step) : 1;
  9234. return step * tickInterval;
  9235. };
  9236. if (horiz) {
  9237. autoRotation = !labelOptions.staggerLines && !labelOptions.step && ( // #3971
  9238. defined(rotationOption) ? [rotationOption] :
  9239. slotSize < pick(labelOptions.autoRotationLimit, 80) && labelOptions.autoRotation
  9240. );
  9241. if (autoRotation) {
  9242. // Loop over the given autoRotation options, and determine which gives the best score. The
  9243. // best score is that with the lowest number of steps and a rotation closest to horizontal.
  9244. each(autoRotation, function(rot) {
  9245. var score;
  9246. if (rot === rotationOption || (rot && rot >= -90 && rot <= 90)) { // #3891
  9247. step = getStep(Math.abs(labelMetrics.h / Math.sin(deg2rad * rot)));
  9248. score = step + Math.abs(rot / 360);
  9249. if (score < bestScore) {
  9250. bestScore = score;
  9251. rotation = rot;
  9252. newTickInterval = step;
  9253. }
  9254. }
  9255. });
  9256. }
  9257. } else if (!labelOptions.step) { // #4411
  9258. newTickInterval = getStep(labelMetrics.h);
  9259. }
  9260. this.autoRotation = autoRotation;
  9261. this.labelRotation = pick(rotation, rotationOption);
  9262. return newTickInterval;
  9263. },
  9264. /**
  9265. * Get the general slot width for this axis. This may change between the pre-render (from Axis.getOffset)
  9266. * and the final tick rendering and placement (#5086).
  9267. */
  9268. getSlotWidth: function() {
  9269. var chart = this.chart,
  9270. horiz = this.horiz,
  9271. labelOptions = this.options.labels,
  9272. slotCount = Math.max(this.tickPositions.length - (this.categories ? 0 : 1), 1),
  9273. marginLeft = chart.margin[3];
  9274. return (
  9275. horiz &&
  9276. (labelOptions.step || 0) < 2 &&
  9277. !labelOptions.rotation && // #4415
  9278. ((this.staggerLines || 1) * this.len) / slotCount
  9279. ) || (!horiz && (
  9280. (marginLeft && (marginLeft - chart.spacing[3])) ||
  9281. chart.chartWidth * 0.33
  9282. )); // #1580, #1931
  9283. },
  9284. /**
  9285. * Render the axis labels and determine whether ellipsis or rotation need to be applied
  9286. */
  9287. renderUnsquish: function() {
  9288. var chart = this.chart,
  9289. renderer = chart.renderer,
  9290. tickPositions = this.tickPositions,
  9291. ticks = this.ticks,
  9292. labelOptions = this.options.labels,
  9293. horiz = this.horiz,
  9294. slotWidth = this.getSlotWidth(),
  9295. innerWidth = Math.max(1, Math.round(slotWidth - 2 * (labelOptions.padding || 5))),
  9296. attr = {},
  9297. labelMetrics = this.labelMetrics(),
  9298. textOverflowOption = labelOptions.style && labelOptions.style.textOverflow,
  9299. css,
  9300. maxLabelLength = 0,
  9301. label,
  9302. i,
  9303. pos;
  9304. // Set rotation option unless it is "auto", like in gauges
  9305. if (!isString(labelOptions.rotation)) {
  9306. attr.rotation = labelOptions.rotation || 0; // #4443
  9307. }
  9308. // Get the longest label length
  9309. each(tickPositions, function(tick) {
  9310. tick = ticks[tick];
  9311. if (tick && tick.labelLength > maxLabelLength) {
  9312. maxLabelLength = tick.labelLength;
  9313. }
  9314. });
  9315. this.maxLabelLength = maxLabelLength;
  9316. // Handle auto rotation on horizontal axis
  9317. if (this.autoRotation) {
  9318. // Apply rotation only if the label is too wide for the slot, and
  9319. // the label is wider than its height.
  9320. if (maxLabelLength > innerWidth && maxLabelLength > labelMetrics.h) {
  9321. attr.rotation = this.labelRotation;
  9322. } else {
  9323. this.labelRotation = 0;
  9324. }
  9325. // Handle word-wrap or ellipsis on vertical axis
  9326. } else if (slotWidth) {
  9327. // For word-wrap or ellipsis
  9328. css = {
  9329. width: innerWidth + 'px'
  9330. };
  9331. if (!textOverflowOption) {
  9332. css.textOverflow = 'clip';
  9333. // On vertical axis, only allow word wrap if there is room for more lines.
  9334. i = tickPositions.length;
  9335. while (!horiz && i--) {
  9336. pos = tickPositions[i];
  9337. label = ticks[pos].label;
  9338. if (label) {
  9339. // Reset ellipsis in order to get the correct bounding box (#4070)
  9340. if (label.styles && label.styles.textOverflow === 'ellipsis') {
  9341. label.css({
  9342. textOverflow: 'clip'
  9343. });
  9344. // Set the correct width in order to read the bounding box height (#4678, #5034)
  9345. } else if (ticks[pos].labelLength > slotWidth) {
  9346. label.css({
  9347. width: slotWidth + 'px'
  9348. });
  9349. }
  9350. if (label.getBBox().height > this.len / tickPositions.length - (labelMetrics.h - labelMetrics.f)) {
  9351. label.specCss = {
  9352. textOverflow: 'ellipsis'
  9353. };
  9354. }
  9355. }
  9356. }
  9357. }
  9358. }
  9359. // Add ellipsis if the label length is significantly longer than ideal
  9360. if (attr.rotation) {
  9361. css = {
  9362. width: (maxLabelLength > chart.chartHeight * 0.5 ? chart.chartHeight * 0.33 : chart.chartHeight) + 'px'
  9363. };
  9364. if (!textOverflowOption) {
  9365. css.textOverflow = 'ellipsis';
  9366. }
  9367. }
  9368. // Set the explicit or automatic label alignment
  9369. this.labelAlign = labelOptions.align || this.autoLabelAlign(this.labelRotation);
  9370. if (this.labelAlign) {
  9371. attr.align = this.labelAlign;
  9372. }
  9373. // Apply general and specific CSS
  9374. each(tickPositions, function(pos) {
  9375. var tick = ticks[pos],
  9376. label = tick && tick.label;
  9377. if (label) {
  9378. label.attr(attr); // This needs to go before the CSS in old IE (#4502)
  9379. if (css) {
  9380. label.css(merge(css, label.specCss));
  9381. }
  9382. delete label.specCss;
  9383. tick.rotation = attr.rotation;
  9384. }
  9385. });
  9386. // Note: Why is this not part of getLabelPosition?
  9387. this.tickRotCorr = renderer.rotCorr(labelMetrics.b, this.labelRotation || 0, this.side !== 0);
  9388. },
  9389. /**
  9390. * Return true if the axis has associated data
  9391. */
  9392. hasData: function() {
  9393. return this.hasVisibleSeries || (defined(this.min) && defined(this.max) && !!this.tickPositions);
  9394. },
  9395. /**
  9396. * Adds the title defined in axis.options.title.
  9397. * @param {Boolean} display - whether or not to display the title
  9398. */
  9399. addTitle: function(display) {
  9400. var axis = this,
  9401. renderer = axis.chart.renderer,
  9402. horiz = axis.horiz,
  9403. opposite = axis.opposite,
  9404. options = axis.options,
  9405. axisTitleOptions = options.title,
  9406. textAlign;
  9407. if (!axis.axisTitle) {
  9408. textAlign = axisTitleOptions.textAlign;
  9409. if (!textAlign) {
  9410. textAlign = (horiz ? {
  9411. low: 'left',
  9412. middle: 'center',
  9413. high: 'right'
  9414. } : {
  9415. low: opposite ? 'right' : 'left',
  9416. middle: 'center',
  9417. high: opposite ? 'left' : 'right'
  9418. })[axisTitleOptions.align];
  9419. }
  9420. axis.axisTitle = renderer.text(
  9421. axisTitleOptions.text,
  9422. 0,
  9423. 0,
  9424. axisTitleOptions.useHTML
  9425. )
  9426. .attr({
  9427. zIndex: 7,
  9428. rotation: axisTitleOptions.rotation || 0,
  9429. align: textAlign
  9430. })
  9431. .addClass('highcharts-axis-title')
  9432. .css(axisTitleOptions.style)
  9433. .add(axis.axisGroup);
  9434. axis.axisTitle.isNew = true;
  9435. }
  9436. // hide or show the title depending on whether showEmpty is set
  9437. axis.axisTitle[display ? 'show' : 'hide'](true);
  9438. },
  9439. /**
  9440. * Generates a tick for initial positioning.
  9441. *
  9442. * @private
  9443. * @param {number} pos
  9444. * The tick position in axis values.
  9445. * @param {number} i
  9446. * The index of the tick in {@link Axis.tickPositions}.
  9447. */
  9448. generateTick: function(pos) {
  9449. var ticks = this.ticks;
  9450. if (!ticks[pos]) {
  9451. ticks[pos] = new Tick(this, pos);
  9452. } else {
  9453. ticks[pos].addLabel(); // update labels depending on tick interval
  9454. }
  9455. },
  9456. /**
  9457. * Render the tick labels to a preliminary position to get their sizes
  9458. */
  9459. getOffset: function() {
  9460. var axis = this,
  9461. chart = axis.chart,
  9462. renderer = chart.renderer,
  9463. options = axis.options,
  9464. tickPositions = axis.tickPositions,
  9465. ticks = axis.ticks,
  9466. horiz = axis.horiz,
  9467. side = axis.side,
  9468. invertedSide = chart.inverted && !axis.isZAxis ? [1, 0, 3, 2][side] : side,
  9469. hasData,
  9470. showAxis,
  9471. titleOffset = 0,
  9472. titleOffsetOption,
  9473. titleMargin = 0,
  9474. axisTitleOptions = options.title,
  9475. labelOptions = options.labels,
  9476. labelOffset = 0, // reset
  9477. labelOffsetPadded,
  9478. axisOffset = chart.axisOffset,
  9479. clipOffset = chart.clipOffset,
  9480. clip,
  9481. directionFactor = [-1, 1, 1, -1][side],
  9482. className = options.className,
  9483. axisParent = axis.axisParent, // Used in color axis
  9484. lineHeightCorrection,
  9485. tickSize = this.tickSize('tick');
  9486. // For reuse in Axis.render
  9487. hasData = axis.hasData();
  9488. axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
  9489. // Set/reset staggerLines
  9490. axis.staggerLines = axis.horiz && labelOptions.staggerLines;
  9491. // Create the axisGroup and gridGroup elements on first iteration
  9492. if (!axis.axisGroup) {
  9493. axis.gridGroup = renderer.g('grid')
  9494. .attr({
  9495. zIndex: options.gridZIndex || 1
  9496. })
  9497. .addClass('highcharts-' + this.coll.toLowerCase() + '-grid ' + (className || ''))
  9498. .add(axisParent);
  9499. axis.axisGroup = renderer.g('axis')
  9500. .attr({
  9501. zIndex: options.zIndex || 2
  9502. })
  9503. .addClass('highcharts-' + this.coll.toLowerCase() + ' ' + (className || ''))
  9504. .add(axisParent);
  9505. axis.labelGroup = renderer.g('axis-labels')
  9506. .attr({
  9507. zIndex: labelOptions.zIndex || 7
  9508. })
  9509. .addClass('highcharts-' + axis.coll.toLowerCase() + '-labels ' + (className || ''))
  9510. .add(axisParent);
  9511. }
  9512. if (hasData || axis.isLinked) {
  9513. // Generate ticks
  9514. each(tickPositions, function(pos, i) {
  9515. // i is not used here, but may be used in overrides
  9516. axis.generateTick(pos, i);
  9517. });
  9518. axis.renderUnsquish();
  9519. // Left side must be align: right and right side must have align: left for labels
  9520. if (labelOptions.reserveSpace !== false && (side === 0 || side === 2 || {
  9521. 1: 'left',
  9522. 3: 'right'
  9523. }[side] === axis.labelAlign || axis.labelAlign === 'center')) {
  9524. each(tickPositions, function(pos) {
  9525. // get the highest offset
  9526. labelOffset = Math.max(
  9527. ticks[pos].getLabelSize(),
  9528. labelOffset
  9529. );
  9530. });
  9531. }
  9532. if (axis.staggerLines) {
  9533. labelOffset *= axis.staggerLines;
  9534. axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1);
  9535. }
  9536. } else { // doesn't have data
  9537. objectEach(ticks, function(tick, n) {
  9538. tick.destroy();
  9539. delete ticks[n];
  9540. });
  9541. }
  9542. if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) {
  9543. axis.addTitle(showAxis);
  9544. if (showAxis && axisTitleOptions.reserveSpace !== false) {
  9545. axis.titleOffset = titleOffset =
  9546. axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
  9547. titleOffsetOption = axisTitleOptions.offset;
  9548. titleMargin = defined(titleOffsetOption) ? 0 : pick(axisTitleOptions.margin, horiz ? 5 : 10);
  9549. }
  9550. }
  9551. // Render the axis line
  9552. axis.renderLine();
  9553. // handle automatic or user set offset
  9554. axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
  9555. axis.tickRotCorr = axis.tickRotCorr || {
  9556. x: 0,
  9557. y: 0
  9558. }; // polar
  9559. if (side === 0) {
  9560. lineHeightCorrection = -axis.labelMetrics().h;
  9561. } else if (side === 2) {
  9562. lineHeightCorrection = axis.tickRotCorr.y;
  9563. } else {
  9564. lineHeightCorrection = 0;
  9565. }
  9566. // Find the padded label offset
  9567. labelOffsetPadded = Math.abs(labelOffset) + titleMargin;
  9568. if (labelOffset) {
  9569. labelOffsetPadded -= lineHeightCorrection;
  9570. labelOffsetPadded += directionFactor * (horiz ? pick(labelOptions.y, axis.tickRotCorr.y + directionFactor * 8) : labelOptions.x);
  9571. }
  9572. axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded);
  9573. axisOffset[side] = Math.max(
  9574. axisOffset[side],
  9575. axis.axisTitleMargin + titleOffset + directionFactor * axis.offset,
  9576. labelOffsetPadded, // #3027
  9577. hasData && tickPositions.length && tickSize ?
  9578. tickSize[0] + directionFactor * axis.offset :
  9579. 0 // #4866
  9580. );
  9581. // Decide the clipping needed to keep the graph inside the plot area and
  9582. // axis lines
  9583. clip = Math.floor(axis.axisLine.strokeWidth() / 2) * 2; // #4308, #4371
  9584. if (options.offset > 0) {
  9585. clip -= options.offset * 2;
  9586. }
  9587. clipOffset[invertedSide] = Math.max(
  9588. clipOffset[invertedSide] || clip,
  9589. clip
  9590. );
  9591. },
  9592. /**
  9593. * Internal function to get the path for the axis line. Extended for polar
  9594. * charts.
  9595. *
  9596. * @param {Number} lineWidth
  9597. * The line width in pixels.
  9598. * @return {Array}
  9599. * The SVG path definition in array form.
  9600. */
  9601. getLinePath: function(lineWidth) {
  9602. var chart = this.chart,
  9603. opposite = this.opposite,
  9604. offset = this.offset,
  9605. horiz = this.horiz,
  9606. lineLeft = this.left + (opposite ? this.width : 0) + offset,
  9607. lineTop = chart.chartHeight - this.bottom -
  9608. (opposite ? this.height : 0) + offset;
  9609. if (opposite) {
  9610. lineWidth *= -1; // crispify the other way - #1480, #1687
  9611. }
  9612. return chart.renderer
  9613. .crispLine([
  9614. 'M',
  9615. horiz ?
  9616. this.left :
  9617. lineLeft,
  9618. horiz ?
  9619. lineTop :
  9620. this.top,
  9621. 'L',
  9622. horiz ?
  9623. chart.chartWidth - this.right :
  9624. lineLeft,
  9625. horiz ?
  9626. lineTop :
  9627. chart.chartHeight - this.bottom
  9628. ], lineWidth);
  9629. },
  9630. /**
  9631. * Render the axis line
  9632. */
  9633. renderLine: function() {
  9634. if (!this.axisLine) {
  9635. this.axisLine = this.chart.renderer.path()
  9636. .addClass('highcharts-axis-line')
  9637. .add(this.axisGroup);
  9638. this.axisLine.attr({
  9639. stroke: this.options.lineColor,
  9640. 'stroke-width': this.options.lineWidth,
  9641. zIndex: 7
  9642. });
  9643. }
  9644. },
  9645. /**
  9646. * Position the title
  9647. */
  9648. getTitlePosition: function() {
  9649. // compute anchor points for each of the title align options
  9650. var horiz = this.horiz,
  9651. axisLeft = this.left,
  9652. axisTop = this.top,
  9653. axisLength = this.len,
  9654. axisTitleOptions = this.options.title,
  9655. margin = horiz ? axisLeft : axisTop,
  9656. opposite = this.opposite,
  9657. offset = this.offset,
  9658. xOption = axisTitleOptions.x || 0,
  9659. yOption = axisTitleOptions.y || 0,
  9660. fontSize = this.chart.renderer.fontMetrics(axisTitleOptions.style && axisTitleOptions.style.fontSize, this.axisTitle).f,
  9661. // the position in the length direction of the axis
  9662. alongAxis = {
  9663. low: margin + (horiz ? 0 : axisLength),
  9664. middle: margin + axisLength / 2,
  9665. high: margin + (horiz ? axisLength : 0)
  9666. }[axisTitleOptions.align],
  9667. // the position in the perpendicular direction of the axis
  9668. offAxis = (horiz ? axisTop + this.height : axisLeft) +
  9669. (horiz ? 1 : -1) * // horizontal axis reverses the margin
  9670. (opposite ? -1 : 1) * // so does opposite axes
  9671. this.axisTitleMargin +
  9672. (this.side === 2 ? fontSize : 0);
  9673. return {
  9674. x: horiz ?
  9675. alongAxis + xOption : offAxis + (opposite ? this.width : 0) + offset + xOption,
  9676. y: horiz ?
  9677. offAxis + yOption - (opposite ? this.height : 0) + offset : alongAxis + yOption
  9678. };
  9679. },
  9680. /**
  9681. * Render a minor tick into the given position. If a minor tick already
  9682. * exists in this position, move it.
  9683. * @param {number} pos - The position in axis values.
  9684. */
  9685. renderMinorTick: function(pos) {
  9686. var slideInTicks = this.chart.hasRendered && isNumber(this.oldMin),
  9687. minorTicks = this.minorTicks;
  9688. if (!minorTicks[pos]) {
  9689. minorTicks[pos] = new Tick(this, pos, 'minor');
  9690. }
  9691. // Render new ticks in old position
  9692. if (slideInTicks && minorTicks[pos].isNew) {
  9693. minorTicks[pos].render(null, true);
  9694. }
  9695. minorTicks[pos].render(null, false, 1);
  9696. },
  9697. /**
  9698. * Render a major tick into the given position. If a tick already exists
  9699. * in this position, move it.
  9700. * @param {number} pos - The position in axis values
  9701. * @param {number} i - The tick index
  9702. */
  9703. renderTick: function(pos, i) {
  9704. var isLinked = this.isLinked,
  9705. ticks = this.ticks,
  9706. slideInTicks = this.chart.hasRendered && isNumber(this.oldMin);
  9707. // Linked axes need an extra check to find out if
  9708. if (!isLinked || (pos >= this.min && pos <= this.max)) {
  9709. if (!ticks[pos]) {
  9710. ticks[pos] = new Tick(this, pos);
  9711. }
  9712. // render new ticks in old position
  9713. if (slideInTicks && ticks[pos].isNew) {
  9714. ticks[pos].render(i, true, 0.1);
  9715. }
  9716. ticks[pos].render(i);
  9717. }
  9718. },
  9719. /**
  9720. * Render the axis
  9721. */
  9722. render: function() {
  9723. var axis = this,
  9724. chart = axis.chart,
  9725. renderer = chart.renderer,
  9726. options = axis.options,
  9727. isLog = axis.isLog,
  9728. lin2log = axis.lin2log,
  9729. isLinked = axis.isLinked,
  9730. tickPositions = axis.tickPositions,
  9731. axisTitle = axis.axisTitle,
  9732. ticks = axis.ticks,
  9733. minorTicks = axis.minorTicks,
  9734. alternateBands = axis.alternateBands,
  9735. stackLabelOptions = options.stackLabels,
  9736. alternateGridColor = options.alternateGridColor,
  9737. tickmarkOffset = axis.tickmarkOffset,
  9738. axisLine = axis.axisLine,
  9739. showAxis = axis.showAxis,
  9740. animation = animObject(renderer.globalAnimation),
  9741. from,
  9742. to;
  9743. // Reset
  9744. axis.labelEdge.length = 0;
  9745. //axis.justifyToPlot = overflow === 'justify';
  9746. axis.overlap = false;
  9747. // Mark all elements inActive before we go over and mark the active ones
  9748. each([ticks, minorTicks, alternateBands], function(coll) {
  9749. objectEach(coll, function(tick) {
  9750. tick.isActive = false;
  9751. });
  9752. });
  9753. // If the series has data draw the ticks. Else only the line and title
  9754. if (axis.hasData() || isLinked) {
  9755. // minor ticks
  9756. if (axis.minorTickInterval && !axis.categories) {
  9757. each(axis.getMinorTickPositions(), function(pos) {
  9758. axis.renderMinorTick(pos);
  9759. });
  9760. }
  9761. // Major ticks. Pull out the first item and render it last so that
  9762. // we can get the position of the neighbour label. #808.
  9763. if (tickPositions.length) { // #1300
  9764. each(tickPositions, function(pos, i) {
  9765. axis.renderTick(pos, i);
  9766. });
  9767. // In a categorized axis, the tick marks are displayed between labels. So
  9768. // we need to add a tick mark and grid line at the left edge of the X axis.
  9769. if (tickmarkOffset && (axis.min === 0 || axis.single)) {
  9770. if (!ticks[-1]) {
  9771. ticks[-1] = new Tick(axis, -1, null, true);
  9772. }
  9773. ticks[-1].render(-1);
  9774. }
  9775. }
  9776. // alternate grid color
  9777. if (alternateGridColor) {
  9778. each(tickPositions, function(pos, i) {
  9779. to = tickPositions[i + 1] !== undefined ? tickPositions[i + 1] + tickmarkOffset : axis.max - tickmarkOffset;
  9780. if (i % 2 === 0 && pos < axis.max && to <= axis.max + (chart.polar ? -tickmarkOffset : tickmarkOffset)) { // #2248, #4660
  9781. if (!alternateBands[pos]) {
  9782. alternateBands[pos] = new H.PlotLineOrBand(axis);
  9783. }
  9784. from = pos + tickmarkOffset; // #949
  9785. alternateBands[pos].options = {
  9786. from: isLog ? lin2log(from) : from,
  9787. to: isLog ? lin2log(to) : to,
  9788. color: alternateGridColor
  9789. };
  9790. alternateBands[pos].render();
  9791. alternateBands[pos].isActive = true;
  9792. }
  9793. });
  9794. }
  9795. // custom plot lines and bands
  9796. if (!axis._addedPlotLB) { // only first time
  9797. each((options.plotLines || []).concat(options.plotBands || []), function(plotLineOptions) {
  9798. axis.addPlotBandOrLine(plotLineOptions);
  9799. });
  9800. axis._addedPlotLB = true;
  9801. }
  9802. } // end if hasData
  9803. // Remove inactive ticks
  9804. each([ticks, minorTicks, alternateBands], function(coll) {
  9805. var i,
  9806. forDestruction = [],
  9807. delay = animation.duration,
  9808. destroyInactiveItems = function() {
  9809. i = forDestruction.length;
  9810. while (i--) {
  9811. // When resizing rapidly, the same items may be destroyed in different timeouts,
  9812. // or the may be reactivated
  9813. if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) {
  9814. coll[forDestruction[i]].destroy();
  9815. delete coll[forDestruction[i]];
  9816. }
  9817. }
  9818. };
  9819. objectEach(coll, function(tick, pos) {
  9820. if (!tick.isActive) {
  9821. // Render to zero opacity
  9822. tick.render(pos, false, 0);
  9823. tick.isActive = false;
  9824. forDestruction.push(pos);
  9825. }
  9826. });
  9827. // When the objects are finished fading out, destroy them
  9828. syncTimeout(
  9829. destroyInactiveItems,
  9830. coll === alternateBands || !chart.hasRendered || !delay ? 0 : delay
  9831. );
  9832. });
  9833. // Set the axis line path
  9834. if (axisLine) {
  9835. axisLine[axisLine.isPlaced ? 'animate' : 'attr']({
  9836. d: this.getLinePath(axisLine.strokeWidth())
  9837. });
  9838. axisLine.isPlaced = true;
  9839. // Show or hide the line depending on options.showEmpty
  9840. axisLine[showAxis ? 'show' : 'hide'](true);
  9841. }
  9842. if (axisTitle && showAxis) {
  9843. var titleXy = axis.getTitlePosition();
  9844. if (isNumber(titleXy.y)) {
  9845. axisTitle[axisTitle.isNew ? 'attr' : 'animate'](titleXy);
  9846. axisTitle.isNew = false;
  9847. } else {
  9848. axisTitle.attr('y', -9999);
  9849. axisTitle.isNew = true;
  9850. }
  9851. }
  9852. // Stacked totals:
  9853. if (stackLabelOptions && stackLabelOptions.enabled) {
  9854. axis.renderStackTotals();
  9855. }
  9856. // End stacked totals
  9857. axis.isDirty = false;
  9858. },
  9859. /**
  9860. * Redraw the axis to reflect changes in the data or axis extremes
  9861. */
  9862. redraw: function() {
  9863. if (this.visible) {
  9864. // render the axis
  9865. this.render();
  9866. // move plot lines and bands
  9867. each(this.plotLinesAndBands, function(plotLine) {
  9868. plotLine.render();
  9869. });
  9870. }
  9871. // mark associated series as dirty and ready for redraw
  9872. each(this.series, function(series) {
  9873. series.isDirty = true;
  9874. });
  9875. },
  9876. // Properties to survive after destroy, needed for Axis.update (#4317,
  9877. // #5773, #5881).
  9878. keepProps: ['extKey', 'hcEvents', 'names', 'series', 'userMax', 'userMin'],
  9879. /**
  9880. * Destroys an Axis instance. See {@link Axis#remove} for the API endpoint
  9881. * to fully remove the axis.
  9882. *
  9883. * @private
  9884. * @param {Boolean} keepEvents
  9885. * Whether to preserve events, used internally in Axis.update.
  9886. */
  9887. destroy: function(keepEvents) {
  9888. var axis = this,
  9889. stacks = axis.stacks,
  9890. plotLinesAndBands = axis.plotLinesAndBands,
  9891. plotGroup,
  9892. i;
  9893. // Remove the events
  9894. if (!keepEvents) {
  9895. removeEvent(axis);
  9896. }
  9897. // Destroy each stack total
  9898. objectEach(stacks, function(stack, stackKey) {
  9899. destroyObjectProperties(stack);
  9900. stacks[stackKey] = null;
  9901. });
  9902. // Destroy collections
  9903. each([axis.ticks, axis.minorTicks, axis.alternateBands], function(coll) {
  9904. destroyObjectProperties(coll);
  9905. });
  9906. if (plotLinesAndBands) {
  9907. i = plotLinesAndBands.length;
  9908. while (i--) { // #1975
  9909. plotLinesAndBands[i].destroy();
  9910. }
  9911. }
  9912. // Destroy local variables
  9913. each(['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup', 'gridGroup', 'labelGroup', 'cross'], function(prop) {
  9914. if (axis[prop]) {
  9915. axis[prop] = axis[prop].destroy();
  9916. }
  9917. });
  9918. // Destroy each generated group for plotlines and plotbands
  9919. for (plotGroup in axis.plotLinesAndBandsGroups) {
  9920. axis.plotLinesAndBandsGroups[plotGroup] = axis.plotLinesAndBandsGroups[plotGroup].destroy();
  9921. }
  9922. // Delete all properties and fall back to the prototype.
  9923. objectEach(axis, function(val, key) {
  9924. if (inArray(key, axis.keepProps) === -1) {
  9925. delete axis[key];
  9926. }
  9927. });
  9928. },
  9929. /**
  9930. * Internal function to draw a crosshair.
  9931. *
  9932. * @param {PointerEvent} [e]
  9933. * The event arguments from the modified pointer event, extended
  9934. * with `chartX` and `chartY`
  9935. * @param {Point} [point]
  9936. * The Point object if the crosshair snaps to points.
  9937. */
  9938. drawCrosshair: function(e, point) {
  9939. var path,
  9940. options = this.crosshair,
  9941. snap = pick(options.snap, true),
  9942. pos,
  9943. categorized,
  9944. graphic = this.cross;
  9945. // Use last available event when updating non-snapped crosshairs without
  9946. // mouse interaction (#5287)
  9947. if (!e) {
  9948. e = this.cross && this.cross.e;
  9949. }
  9950. if (
  9951. // Disabled in options
  9952. !this.crosshair ||
  9953. // Snap
  9954. ((defined(point) || !snap) === false)
  9955. ) {
  9956. this.hideCrosshair();
  9957. } else {
  9958. // Get the path
  9959. if (!snap) {
  9960. pos = e && (this.horiz ? e.chartX - this.pos : this.len - e.chartY + this.pos);
  9961. } else if (defined(point)) {
  9962. pos = this.isXAxis ? point.plotX : this.len - point.plotY; // #3834
  9963. }
  9964. if (defined(pos)) {
  9965. path = this.getPlotLinePath(
  9966. // First argument, value, only used on radial
  9967. point && (this.isXAxis ? point.x : pick(point.stackY, point.y)),
  9968. null,
  9969. null,
  9970. null,
  9971. pos // Translated position
  9972. ) || null; // #3189
  9973. }
  9974. if (!defined(path)) {
  9975. this.hideCrosshair();
  9976. return;
  9977. }
  9978. categorized = this.categories && !this.isRadial;
  9979. // Draw the cross
  9980. if (!graphic) {
  9981. this.cross = graphic = this.chart.renderer
  9982. .path()
  9983. .addClass('highcharts-crosshair highcharts-crosshair-' +
  9984. (categorized ? 'category ' : 'thin ') + options.className)
  9985. .attr({
  9986. zIndex: pick(options.zIndex, 2)
  9987. })
  9988. .add();
  9989. // Presentational attributes
  9990. graphic.attr({
  9991. 'stroke': options.color || (categorized ? color('#ccd6eb').setOpacity(0.25).get() : '#cccccc'),
  9992. 'stroke-width': pick(options.width, 1)
  9993. });
  9994. if (options.dashStyle) {
  9995. graphic.attr({
  9996. dashstyle: options.dashStyle
  9997. });
  9998. }
  9999. }
  10000. graphic.show().attr({
  10001. d: path
  10002. });
  10003. if (categorized && !options.width) {
  10004. graphic.attr({
  10005. 'stroke-width': this.transA
  10006. });
  10007. }
  10008. this.cross.e = e;
  10009. }
  10010. },
  10011. /**
  10012. * Hide the crosshair.
  10013. */
  10014. hideCrosshair: function() {
  10015. if (this.cross) {
  10016. this.cross.hide();
  10017. }
  10018. }
  10019. }); // end Axis
  10020. H.Axis = Axis;
  10021. return Axis;
  10022. }(Highcharts));
  10023. (function(H) {
  10024. /**
  10025. * (c) 2010-2017 Torstein Honsi
  10026. *
  10027. * License: www.highcharts.com/license
  10028. */
  10029. var Axis = H.Axis,
  10030. Date = H.Date,
  10031. dateFormat = H.dateFormat,
  10032. defaultOptions = H.defaultOptions,
  10033. defined = H.defined,
  10034. each = H.each,
  10035. extend = H.extend,
  10036. getMagnitude = H.getMagnitude,
  10037. getTZOffset = H.getTZOffset,
  10038. normalizeTickInterval = H.normalizeTickInterval,
  10039. pick = H.pick,
  10040. timeUnits = H.timeUnits;
  10041. /**
  10042. * Set the tick positions to a time unit that makes sense, for example
  10043. * on the first of each month or on every Monday. Return an array
  10044. * with the time positions. Used in datetime axes as well as for grouping
  10045. * data on a datetime axis.
  10046. *
  10047. * @param {Object} normalizedInterval The interval in axis values (ms) and the count
  10048. * @param {Number} min The minimum in axis values
  10049. * @param {Number} max The maximum in axis values
  10050. * @param {Number} startOfWeek
  10051. */
  10052. Axis.prototype.getTimeTicks = function(normalizedInterval, min, max, startOfWeek) {
  10053. var tickPositions = [],
  10054. i,
  10055. higherRanks = {},
  10056. useUTC = defaultOptions.global.useUTC,
  10057. minYear, // used in months and years as a basis for Date.UTC()
  10058. // When crossing DST, use the max. Resolves #6278.
  10059. minDate = new Date(min - Math.max(getTZOffset(min), getTZOffset(max))),
  10060. makeTime = Date.hcMakeTime,
  10061. interval = normalizedInterval.unitRange,
  10062. count = normalizedInterval.count,
  10063. variableDayLength;
  10064. if (defined(min)) { // #1300
  10065. minDate[Date.hcSetMilliseconds](interval >= timeUnits.second ? 0 : // #3935
  10066. count * Math.floor(minDate.getMilliseconds() / count)); // #3652, #3654
  10067. if (interval >= timeUnits.second) { // second
  10068. minDate[Date.hcSetSeconds](interval >= timeUnits.minute ? 0 : // #3935
  10069. count * Math.floor(minDate.getSeconds() / count));
  10070. }
  10071. if (interval >= timeUnits.minute) { // minute
  10072. minDate[Date.hcSetMinutes](interval >= timeUnits.hour ? 0 :
  10073. count * Math.floor(minDate[Date.hcGetMinutes]() / count));
  10074. }
  10075. if (interval >= timeUnits.hour) { // hour
  10076. minDate[Date.hcSetHours](interval >= timeUnits.day ? 0 :
  10077. count * Math.floor(minDate[Date.hcGetHours]() / count));
  10078. }
  10079. if (interval >= timeUnits.day) { // day
  10080. minDate[Date.hcSetDate](interval >= timeUnits.month ? 1 :
  10081. count * Math.floor(minDate[Date.hcGetDate]() / count));
  10082. }
  10083. if (interval >= timeUnits.month) { // month
  10084. minDate[Date.hcSetMonth](interval >= timeUnits.year ? 0 :
  10085. count * Math.floor(minDate[Date.hcGetMonth]() / count));
  10086. minYear = minDate[Date.hcGetFullYear]();
  10087. }
  10088. if (interval >= timeUnits.year) { // year
  10089. minYear -= minYear % count;
  10090. minDate[Date.hcSetFullYear](minYear);
  10091. }
  10092. // week is a special case that runs outside the hierarchy
  10093. if (interval === timeUnits.week) {
  10094. // get start of current week, independent of count
  10095. minDate[Date.hcSetDate](minDate[Date.hcGetDate]() - minDate[Date.hcGetDay]() +
  10096. pick(startOfWeek, 1));
  10097. }
  10098. // Get basics for variable time spans
  10099. minYear = minDate[Date.hcGetFullYear]();
  10100. var minMonth = minDate[Date.hcGetMonth](),
  10101. minDateDate = minDate[Date.hcGetDate](),
  10102. minHours = minDate[Date.hcGetHours]();
  10103. // Handle local timezone offset
  10104. if (Date.hcTimezoneOffset || Date.hcGetTimezoneOffset) {
  10105. // Detect whether we need to take the DST crossover into
  10106. // consideration. If we're crossing over DST, the day length may be
  10107. // 23h or 25h and we need to compute the exact clock time for each
  10108. // tick instead of just adding hours. This comes at a cost, so first
  10109. // we found out if it is needed. #4951.
  10110. variableDayLength =
  10111. (!useUTC || !!Date.hcGetTimezoneOffset) &&
  10112. (
  10113. // Long range, assume we're crossing over.
  10114. max - min > 4 * timeUnits.month ||
  10115. // Short range, check if min and max are in different time
  10116. // zones.
  10117. getTZOffset(min) !== getTZOffset(max)
  10118. );
  10119. // Adjust minDate to the offset date
  10120. minDate = minDate.getTime();
  10121. minDate = new Date(minDate + getTZOffset(minDate));
  10122. }
  10123. // Iterate and add tick positions at appropriate values
  10124. var time = minDate.getTime();
  10125. i = 1;
  10126. while (time < max) {
  10127. tickPositions.push(time);
  10128. // if the interval is years, use Date.UTC to increase years
  10129. if (interval === timeUnits.year) {
  10130. time = makeTime(minYear + i * count, 0);
  10131. // if the interval is months, use Date.UTC to increase months
  10132. } else if (interval === timeUnits.month) {
  10133. time = makeTime(minYear, minMonth + i * count);
  10134. // if we're using global time, the interval is not fixed as it jumps
  10135. // one hour at the DST crossover
  10136. } else if (variableDayLength && (interval === timeUnits.day || interval === timeUnits.week)) {
  10137. time = makeTime(minYear, minMonth, minDateDate +
  10138. i * count * (interval === timeUnits.day ? 1 : 7));
  10139. } else if (variableDayLength && interval === timeUnits.hour) {
  10140. time = makeTime(minYear, minMonth, minDateDate, minHours + i * count);
  10141. // else, the interval is fixed and we use simple addition
  10142. } else {
  10143. time += interval * count;
  10144. }
  10145. i++;
  10146. }
  10147. // push the last time
  10148. tickPositions.push(time);
  10149. // Handle higher ranks. Mark new days if the time is on midnight
  10150. // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold to
  10151. // prevent looping over dense data grouping (#6156).
  10152. if (interval <= timeUnits.hour && tickPositions.length < 10000) {
  10153. each(tickPositions, function(time) {
  10154. if (
  10155. // Speed optimization, no need to run dateFormat unless
  10156. // we're on a full or half hour
  10157. time % 1800000 === 0 &&
  10158. // Check for local or global midnight
  10159. dateFormat('%H%M%S%L', time) === '000000000'
  10160. ) {
  10161. higherRanks[time] = 'day';
  10162. }
  10163. });
  10164. }
  10165. }
  10166. // record information on the chosen unit - for dynamic label formatter
  10167. tickPositions.info = extend(normalizedInterval, {
  10168. higherRanks: higherRanks,
  10169. totalRange: interval * count
  10170. });
  10171. return tickPositions;
  10172. };
  10173. /**
  10174. * Get a normalized tick interval for dates. Returns a configuration object with
  10175. * unit range (interval), count and name. Used to prepare data for getTimeTicks.
  10176. * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
  10177. * of segments in stock charts, the normalizing logic was extracted in order to
  10178. * prevent it for running over again for each segment having the same interval.
  10179. * #662, #697.
  10180. */
  10181. Axis.prototype.normalizeTimeTickInterval = function(tickInterval, unitsOption) {
  10182. var units = unitsOption || [
  10183. [
  10184. 'millisecond', // unit name
  10185. [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
  10186. ],
  10187. [
  10188. 'second', [1, 2, 5, 10, 15, 30]
  10189. ],
  10190. [
  10191. 'minute', [1, 2, 5, 10, 15, 30]
  10192. ],
  10193. [
  10194. 'hour', [1, 2, 3, 4, 6, 8, 12]
  10195. ],
  10196. [
  10197. 'day', [1, 2]
  10198. ],
  10199. [
  10200. 'week', [1, 2]
  10201. ],
  10202. [
  10203. 'month', [1, 2, 3, 4, 6]
  10204. ],
  10205. [
  10206. 'year',
  10207. null
  10208. ]
  10209. ],
  10210. unit = units[units.length - 1], // default unit is years
  10211. interval = timeUnits[unit[0]],
  10212. multiples = unit[1],
  10213. count,
  10214. i;
  10215. // loop through the units to find the one that best fits the tickInterval
  10216. for (i = 0; i < units.length; i++) {
  10217. unit = units[i];
  10218. interval = timeUnits[unit[0]];
  10219. multiples = unit[1];
  10220. if (units[i + 1]) {
  10221. // lessThan is in the middle between the highest multiple and the next unit.
  10222. var lessThan = (interval * multiples[multiples.length - 1] +
  10223. timeUnits[units[i + 1][0]]) / 2;
  10224. // break and keep the current unit
  10225. if (tickInterval <= lessThan) {
  10226. break;
  10227. }
  10228. }
  10229. }
  10230. // prevent 2.5 years intervals, though 25, 250 etc. are allowed
  10231. if (interval === timeUnits.year && tickInterval < 5 * interval) {
  10232. multiples = [1, 2, 5];
  10233. }
  10234. // get the count
  10235. count = normalizeTickInterval(
  10236. tickInterval / interval,
  10237. multiples,
  10238. unit[0] === 'year' ? Math.max(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360
  10239. );
  10240. return {
  10241. unitRange: interval,
  10242. count: count,
  10243. unitName: unit[0]
  10244. };
  10245. };
  10246. }(Highcharts));
  10247. (function(H) {
  10248. /**
  10249. * (c) 2010-2017 Torstein Honsi
  10250. *
  10251. * License: www.highcharts.com/license
  10252. */
  10253. var Axis = H.Axis,
  10254. getMagnitude = H.getMagnitude,
  10255. map = H.map,
  10256. normalizeTickInterval = H.normalizeTickInterval,
  10257. pick = H.pick;
  10258. /**
  10259. * Methods defined on the Axis prototype
  10260. */
  10261. /**
  10262. * Set the tick positions of a logarithmic axis
  10263. */
  10264. Axis.prototype.getLogTickPositions = function(interval, min, max, minor) {
  10265. var axis = this,
  10266. options = axis.options,
  10267. axisLength = axis.len,
  10268. lin2log = axis.lin2log,
  10269. log2lin = axis.log2lin,
  10270. // Since we use this method for both major and minor ticks,
  10271. // use a local variable and return the result
  10272. positions = [];
  10273. // Reset
  10274. if (!minor) {
  10275. axis._minorAutoInterval = null;
  10276. }
  10277. // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
  10278. if (interval >= 0.5) {
  10279. interval = Math.round(interval);
  10280. positions = axis.getLinearTickPositions(interval, min, max);
  10281. // Second case: We need intermediary ticks. For example
  10282. // 1, 2, 4, 6, 8, 10, 20, 40 etc.
  10283. } else if (interval >= 0.08) {
  10284. var roundedMin = Math.floor(min),
  10285. intermediate,
  10286. i,
  10287. j,
  10288. len,
  10289. pos,
  10290. lastPos,
  10291. break2;
  10292. if (interval > 0.3) {
  10293. intermediate = [1, 2, 4];
  10294. } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
  10295. intermediate = [1, 2, 4, 6, 8];
  10296. } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
  10297. intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  10298. }
  10299. for (i = roundedMin; i < max + 1 && !break2; i++) {
  10300. len = intermediate.length;
  10301. for (j = 0; j < len && !break2; j++) {
  10302. pos = log2lin(lin2log(i) * intermediate[j]);
  10303. if (pos > min && (!minor || lastPos <= max) && lastPos !== undefined) { // #1670, lastPos is #3113
  10304. positions.push(lastPos);
  10305. }
  10306. if (lastPos > max) {
  10307. break2 = true;
  10308. }
  10309. lastPos = pos;
  10310. }
  10311. }
  10312. // Third case: We are so deep in between whole logarithmic values that
  10313. // we might as well handle the tick positions like a linear axis. For
  10314. // example 1.01, 1.02, 1.03, 1.04.
  10315. } else {
  10316. var realMin = lin2log(min),
  10317. realMax = lin2log(max),
  10318. tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'],
  10319. filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
  10320. tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
  10321. totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
  10322. interval = pick(
  10323. filteredTickIntervalOption,
  10324. axis._minorAutoInterval,
  10325. (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
  10326. );
  10327. interval = normalizeTickInterval(
  10328. interval,
  10329. null,
  10330. getMagnitude(interval)
  10331. );
  10332. positions = map(axis.getLinearTickPositions(
  10333. interval,
  10334. realMin,
  10335. realMax
  10336. ), log2lin);
  10337. if (!minor) {
  10338. axis._minorAutoInterval = interval / 5;
  10339. }
  10340. }
  10341. // Set the axis-level tickInterval variable
  10342. if (!minor) {
  10343. axis.tickInterval = interval;
  10344. }
  10345. return positions;
  10346. };
  10347. Axis.prototype.log2lin = function(num) {
  10348. return Math.log(num) / Math.LN10;
  10349. };
  10350. Axis.prototype.lin2log = function(num) {
  10351. return Math.pow(10, num);
  10352. };
  10353. }(Highcharts));
  10354. (function(H, Axis) {
  10355. /**
  10356. * (c) 2010-2017 Torstein Honsi
  10357. *
  10358. * License: www.highcharts.com/license
  10359. */
  10360. var arrayMax = H.arrayMax,
  10361. arrayMin = H.arrayMin,
  10362. defined = H.defined,
  10363. destroyObjectProperties = H.destroyObjectProperties,
  10364. each = H.each,
  10365. erase = H.erase,
  10366. merge = H.merge,
  10367. pick = H.pick;
  10368. /*
  10369. * The object wrapper for plot lines and plot bands
  10370. * @param {Object} options
  10371. */
  10372. H.PlotLineOrBand = function(axis, options) {
  10373. this.axis = axis;
  10374. if (options) {
  10375. this.options = options;
  10376. this.id = options.id;
  10377. }
  10378. };
  10379. H.PlotLineOrBand.prototype = {
  10380. /**
  10381. * Render the plot line or plot band. If it is already existing,
  10382. * move it.
  10383. */
  10384. render: function() {
  10385. var plotLine = this,
  10386. axis = plotLine.axis,
  10387. horiz = axis.horiz,
  10388. options = plotLine.options,
  10389. optionsLabel = options.label,
  10390. label = plotLine.label,
  10391. to = options.to,
  10392. from = options.from,
  10393. value = options.value,
  10394. isBand = defined(from) && defined(to),
  10395. isLine = defined(value),
  10396. svgElem = plotLine.svgElem,
  10397. isNew = !svgElem,
  10398. path = [],
  10399. color = options.color,
  10400. zIndex = pick(options.zIndex, 0),
  10401. events = options.events,
  10402. attribs = {
  10403. 'class': 'highcharts-plot-' + (isBand ? 'band ' : 'line ') + (options.className || '')
  10404. },
  10405. groupAttribs = {},
  10406. renderer = axis.chart.renderer,
  10407. groupName = isBand ? 'bands' : 'lines',
  10408. group,
  10409. log2lin = axis.log2lin;
  10410. // logarithmic conversion
  10411. if (axis.isLog) {
  10412. from = log2lin(from);
  10413. to = log2lin(to);
  10414. value = log2lin(value);
  10415. }
  10416. // Set the presentational attributes
  10417. if (isLine) {
  10418. attribs = {
  10419. stroke: color,
  10420. 'stroke-width': options.width
  10421. };
  10422. if (options.dashStyle) {
  10423. attribs.dashstyle = options.dashStyle;
  10424. }
  10425. } else if (isBand) { // plot band
  10426. if (color) {
  10427. attribs.fill = color;
  10428. }
  10429. if (options.borderWidth) {
  10430. attribs.stroke = options.borderColor;
  10431. attribs['stroke-width'] = options.borderWidth;
  10432. }
  10433. }
  10434. // Grouping and zIndex
  10435. groupAttribs.zIndex = zIndex;
  10436. groupName += '-' + zIndex;
  10437. group = axis.plotLinesAndBandsGroups[groupName];
  10438. if (!group) {
  10439. axis.plotLinesAndBandsGroups[groupName] = group = renderer.g('plot-' + groupName)
  10440. .attr(groupAttribs).add();
  10441. }
  10442. // Create the path
  10443. if (isNew) {
  10444. plotLine.svgElem = svgElem =
  10445. renderer
  10446. .path()
  10447. .attr(attribs).add(group);
  10448. }
  10449. // Set the path or return
  10450. if (isLine) {
  10451. path = axis.getPlotLinePath(value, svgElem.strokeWidth());
  10452. } else if (isBand) { // plot band
  10453. path = axis.getPlotBandPath(from, to, options);
  10454. } else {
  10455. return;
  10456. }
  10457. // common for lines and bands
  10458. if (isNew && path && path.length) {
  10459. svgElem.attr({
  10460. d: path
  10461. });
  10462. // events
  10463. if (events) {
  10464. H.objectEach(events, function(event, eventType) {
  10465. svgElem.on(eventType, function(e) {
  10466. events[eventType].apply(plotLine, [e]);
  10467. });
  10468. });
  10469. }
  10470. } else if (svgElem) {
  10471. if (path) {
  10472. svgElem.show();
  10473. svgElem.animate({
  10474. d: path
  10475. });
  10476. } else {
  10477. svgElem.hide();
  10478. if (label) {
  10479. plotLine.label = label = label.destroy();
  10480. }
  10481. }
  10482. }
  10483. // the plot band/line label
  10484. if (optionsLabel && defined(optionsLabel.text) && path && path.length &&
  10485. axis.width > 0 && axis.height > 0 && !path.flat) {
  10486. // apply defaults
  10487. optionsLabel = merge({
  10488. align: horiz && isBand && 'center',
  10489. x: horiz ? !isBand && 4 : 10,
  10490. verticalAlign: !horiz && isBand && 'middle',
  10491. y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
  10492. rotation: horiz && !isBand && 90
  10493. }, optionsLabel);
  10494. this.renderLabel(optionsLabel, path, isBand, zIndex);
  10495. } else if (label) { // move out of sight
  10496. label.hide();
  10497. }
  10498. // chainable
  10499. return plotLine;
  10500. },
  10501. /**
  10502. * Render and align label for plot line or band.
  10503. */
  10504. renderLabel: function(optionsLabel, path, isBand, zIndex) {
  10505. var plotLine = this,
  10506. label = plotLine.label,
  10507. renderer = plotLine.axis.chart.renderer,
  10508. attribs,
  10509. xs,
  10510. ys,
  10511. x,
  10512. y;
  10513. // add the SVG element
  10514. if (!label) {
  10515. attribs = {
  10516. align: optionsLabel.textAlign || optionsLabel.align,
  10517. rotation: optionsLabel.rotation,
  10518. 'class': 'highcharts-plot-' + (isBand ? 'band' : 'line') + '-label ' + (optionsLabel.className || '')
  10519. };
  10520. attribs.zIndex = zIndex;
  10521. plotLine.label = label = renderer.text(
  10522. optionsLabel.text,
  10523. 0,
  10524. 0,
  10525. optionsLabel.useHTML
  10526. )
  10527. .attr(attribs)
  10528. .add();
  10529. label.css(optionsLabel.style);
  10530. }
  10531. // get the bounding box and align the label
  10532. // #3000 changed to better handle choice between plotband or plotline
  10533. xs = [path[1], path[4], (isBand ? path[6] : path[1])];
  10534. ys = [path[2], path[5], (isBand ? path[7] : path[2])];
  10535. x = arrayMin(xs);
  10536. y = arrayMin(ys);
  10537. label.align(optionsLabel, false, {
  10538. x: x,
  10539. y: y,
  10540. width: arrayMax(xs) - x,
  10541. height: arrayMax(ys) - y
  10542. });
  10543. label.show();
  10544. },
  10545. /**
  10546. * Remove the plot line or band
  10547. */
  10548. destroy: function() {
  10549. // remove it from the lookup
  10550. erase(this.axis.plotLinesAndBands, this);
  10551. delete this.axis;
  10552. destroyObjectProperties(this);
  10553. }
  10554. };
  10555. /**
  10556. * Object with members for extending the Axis prototype
  10557. * @todo Extend directly instead of adding object to Highcharts first
  10558. */
  10559. H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
  10560. /**
  10561. * Create the path for a plot band
  10562. */
  10563. getPlotBandPath: function(from, to) {
  10564. var toPath = this.getPlotLinePath(to, null, null, true),
  10565. path = this.getPlotLinePath(from, null, null, true),
  10566. // #4964 check if chart is inverted or plotband is on yAxis
  10567. horiz = this.horiz,
  10568. plus = 1,
  10569. outside =
  10570. (from < this.min && to < this.min) ||
  10571. (from > this.max && to > this.max);
  10572. if (path && toPath) {
  10573. // Flat paths don't need labels (#3836)
  10574. if (outside) {
  10575. path.flat = path.toString() === toPath.toString();
  10576. plus = 0;
  10577. }
  10578. // Add 1 pixel, when coordinates are the same
  10579. path.push(
  10580. horiz && toPath[4] === path[4] ? toPath[4] + plus : toPath[4], !horiz && toPath[5] === path[5] ? toPath[5] + plus : toPath[5],
  10581. horiz && toPath[1] === path[1] ? toPath[1] + plus : toPath[1], !horiz && toPath[2] === path[2] ? toPath[2] + plus : toPath[2]
  10582. );
  10583. } else { // outside the axis area
  10584. path = null;
  10585. }
  10586. return path;
  10587. },
  10588. /**
  10589. * Add a plot band after render time.
  10590. *
  10591. * @param {AxisPlotBandsOptions} options
  10592. * A configuration object for the plot band, as defined in {@link
  10593. * https://api.highcharts.com/highcharts/xAxis.plotBands|
  10594. * xAxis.plotBands}.
  10595. * @return {Object}
  10596. * The added plot band.
  10597. * @sample highcharts/members/axis-addplotband/
  10598. * Toggle the plot band from a button
  10599. */
  10600. addPlotBand: function(options) {
  10601. return this.addPlotBandOrLine(options, 'plotBands');
  10602. },
  10603. /**
  10604. * Add a plot line after render time.
  10605. *
  10606. * @param {AxisPlotLinesOptions} options
  10607. * A configuration object for the plot line, as defined in {@link
  10608. * https://api.highcharts.com/highcharts/xAxis.plotLines|
  10609. * xAxis.plotLines}.
  10610. * @return {Object}
  10611. * The added plot line.
  10612. * @sample highcharts/members/axis-addplotline/
  10613. * Toggle the plot line from a button
  10614. */
  10615. addPlotLine: function(options) {
  10616. return this.addPlotBandOrLine(options, 'plotLines');
  10617. },
  10618. /**
  10619. * Add a plot band or plot line after render time. Called from addPlotBand
  10620. * and addPlotLine internally.
  10621. *
  10622. * @private
  10623. * @param options {AxisPlotLinesOptions|AxisPlotBandsOptions}
  10624. * The plotBand or plotLine configuration object.
  10625. */
  10626. addPlotBandOrLine: function(options, coll) {
  10627. var obj = new H.PlotLineOrBand(this, options).render(),
  10628. userOptions = this.userOptions;
  10629. if (obj) { // #2189
  10630. // Add it to the user options for exporting and Axis.update
  10631. if (coll) {
  10632. userOptions[coll] = userOptions[coll] || [];
  10633. userOptions[coll].push(options);
  10634. }
  10635. this.plotLinesAndBands.push(obj);
  10636. }
  10637. return obj;
  10638. },
  10639. /**
  10640. * Remove a plot band or plot line from the chart by id. Called internally
  10641. * from `removePlotBand` and `removePlotLine`.
  10642. *
  10643. * @private
  10644. * @param {String} id
  10645. */
  10646. removePlotBandOrLine: function(id) {
  10647. var plotLinesAndBands = this.plotLinesAndBands,
  10648. options = this.options,
  10649. userOptions = this.userOptions,
  10650. i = plotLinesAndBands.length;
  10651. while (i--) {
  10652. if (plotLinesAndBands[i].id === id) {
  10653. plotLinesAndBands[i].destroy();
  10654. }
  10655. }
  10656. each([
  10657. options.plotLines || [],
  10658. userOptions.plotLines || [],
  10659. options.plotBands || [],
  10660. userOptions.plotBands || []
  10661. ], function(arr) {
  10662. i = arr.length;
  10663. while (i--) {
  10664. if (arr[i].id === id) {
  10665. erase(arr, arr[i]);
  10666. }
  10667. }
  10668. });
  10669. },
  10670. /**
  10671. * Remove a plot band by its id.
  10672. *
  10673. * @param {String} id
  10674. * The plot band's `id` as given in the original configuration
  10675. * object or in the `addPlotBand` option.
  10676. * @sample highcharts/members/axis-removeplotband/
  10677. * Remove plot band by id
  10678. * @sample highcharts/members/axis-addplotband/
  10679. * Toggle the plot band from a button
  10680. */
  10681. removePlotBand: function(id) {
  10682. this.removePlotBandOrLine(id);
  10683. },
  10684. /**
  10685. * Remove a plot line by its id.
  10686. * @param {String} id
  10687. * The plot line's `id` as given in the original configuration
  10688. * object or in the `addPlotLine` option.
  10689. * @sample highcharts/xaxis/plotlines-id/
  10690. * Remove plot line by id
  10691. * @sample highcharts/members/axis-addplotline/
  10692. * Toggle the plot line from a button
  10693. */
  10694. removePlotLine: function(id) {
  10695. this.removePlotBandOrLine(id);
  10696. }
  10697. });
  10698. }(Highcharts, Axis));
  10699. (function(H) {
  10700. /**
  10701. * (c) 2010-2017 Torstein Honsi
  10702. *
  10703. * License: www.highcharts.com/license
  10704. */
  10705. var dateFormat = H.dateFormat,
  10706. each = H.each,
  10707. extend = H.extend,
  10708. format = H.format,
  10709. isNumber = H.isNumber,
  10710. map = H.map,
  10711. merge = H.merge,
  10712. pick = H.pick,
  10713. splat = H.splat,
  10714. syncTimeout = H.syncTimeout,
  10715. timeUnits = H.timeUnits;
  10716. /**
  10717. * The tooltip object
  10718. * @param {Object} chart The chart instance
  10719. * @param {Object} options Tooltip options
  10720. */
  10721. H.Tooltip = function() {
  10722. this.init.apply(this, arguments);
  10723. };
  10724. H.Tooltip.prototype = {
  10725. init: function(chart, options) {
  10726. // Save the chart and options
  10727. this.chart = chart;
  10728. this.options = options;
  10729. // Keep track of the current series
  10730. //this.currentSeries = undefined;
  10731. // List of crosshairs
  10732. this.crosshairs = [];
  10733. // Current values of x and y when animating
  10734. this.now = {
  10735. x: 0,
  10736. y: 0
  10737. };
  10738. // The tooltip is initially hidden
  10739. this.isHidden = true;
  10740. // Public property for getting the shared state.
  10741. this.split = options.split && !chart.inverted;
  10742. this.shared = options.shared || this.split;
  10743. },
  10744. /**
  10745. * Destroy the single tooltips in a split tooltip.
  10746. * If the tooltip is active then it is not destroyed, unless forced to.
  10747. * @param {boolean} force Force destroy all tooltips.
  10748. * @return {undefined}
  10749. */
  10750. cleanSplit: function(force) {
  10751. each(this.chart.series, function(series) {
  10752. var tt = series && series.tt;
  10753. if (tt) {
  10754. if (!tt.isActive || force) {
  10755. series.tt = tt.destroy();
  10756. } else {
  10757. tt.isActive = false;
  10758. }
  10759. }
  10760. });
  10761. },
  10762. /**
  10763. * Create the Tooltip label element if it doesn't exist, then return the
  10764. * label.
  10765. */
  10766. getLabel: function() {
  10767. var renderer = this.chart.renderer,
  10768. options = this.options;
  10769. if (!this.label) {
  10770. // Create the label
  10771. if (this.split) {
  10772. this.label = renderer.g('tooltip');
  10773. } else {
  10774. this.label = renderer.label(
  10775. '',
  10776. 0,
  10777. 0,
  10778. options.shape || 'callout',
  10779. null,
  10780. null,
  10781. options.useHTML,
  10782. null,
  10783. 'tooltip'
  10784. )
  10785. .attr({
  10786. padding: options.padding,
  10787. r: options.borderRadius
  10788. });
  10789. this.label
  10790. .attr({
  10791. 'fill': options.backgroundColor,
  10792. 'stroke-width': options.borderWidth
  10793. })
  10794. // #2301, #2657
  10795. .css(options.style)
  10796. .shadow(options.shadow);
  10797. }
  10798. this.label
  10799. .attr({
  10800. zIndex: 8
  10801. })
  10802. .add();
  10803. }
  10804. return this.label;
  10805. },
  10806. update: function(options) {
  10807. this.destroy();
  10808. // Update user options (#6218)
  10809. merge(true, this.chart.options.tooltip.userOptions, options);
  10810. this.init(this.chart, merge(true, this.options, options));
  10811. },
  10812. /**
  10813. * Destroy the tooltip and its elements.
  10814. */
  10815. destroy: function() {
  10816. // Destroy and clear local variables
  10817. if (this.label) {
  10818. this.label = this.label.destroy();
  10819. }
  10820. if (this.split && this.tt) {
  10821. this.cleanSplit(this.chart, true);
  10822. this.tt = this.tt.destroy();
  10823. }
  10824. clearTimeout(this.hideTimer);
  10825. clearTimeout(this.tooltipTimeout);
  10826. },
  10827. /**
  10828. * Provide a soft movement for the tooltip
  10829. *
  10830. * @param {Number} x
  10831. * @param {Number} y
  10832. * @private
  10833. */
  10834. move: function(x, y, anchorX, anchorY) {
  10835. var tooltip = this,
  10836. now = tooltip.now,
  10837. animate = tooltip.options.animation !== false && !tooltip.isHidden &&
  10838. // When we get close to the target position, abort animation and land on the right place (#3056)
  10839. (Math.abs(x - now.x) > 1 || Math.abs(y - now.y) > 1),
  10840. skipAnchor = tooltip.followPointer || tooltip.len > 1;
  10841. // Get intermediate values for animation
  10842. extend(now, {
  10843. x: animate ? (2 * now.x + x) / 3 : x,
  10844. y: animate ? (now.y + y) / 2 : y,
  10845. anchorX: skipAnchor ? undefined : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
  10846. anchorY: skipAnchor ? undefined : animate ? (now.anchorY + anchorY) / 2 : anchorY
  10847. });
  10848. // Move to the intermediate value
  10849. tooltip.getLabel().attr(now);
  10850. // Run on next tick of the mouse tracker
  10851. if (animate) {
  10852. // Never allow two timeouts
  10853. clearTimeout(this.tooltipTimeout);
  10854. // Set the fixed interval ticking for the smooth tooltip
  10855. this.tooltipTimeout = setTimeout(function() {
  10856. // The interval function may still be running during destroy,
  10857. // so check that the chart is really there before calling.
  10858. if (tooltip) {
  10859. tooltip.move(x, y, anchorX, anchorY);
  10860. }
  10861. }, 32);
  10862. }
  10863. },
  10864. /**
  10865. * Hide the tooltip
  10866. */
  10867. hide: function(delay) {
  10868. var tooltip = this;
  10869. clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766)
  10870. delay = pick(delay, this.options.hideDelay, 500);
  10871. if (!this.isHidden) {
  10872. this.hideTimer = syncTimeout(function() {
  10873. tooltip.getLabel()[delay ? 'fadeOut' : 'hide']();
  10874. tooltip.isHidden = true;
  10875. }, delay);
  10876. }
  10877. },
  10878. /**
  10879. * Extendable method to get the anchor position of the tooltip
  10880. * from a point or set of points
  10881. */
  10882. getAnchor: function(points, mouseEvent) {
  10883. var ret,
  10884. chart = this.chart,
  10885. inverted = chart.inverted,
  10886. plotTop = chart.plotTop,
  10887. plotLeft = chart.plotLeft,
  10888. plotX = 0,
  10889. plotY = 0,
  10890. yAxis,
  10891. xAxis;
  10892. points = splat(points);
  10893. // Pie uses a special tooltipPos
  10894. ret = points[0].tooltipPos;
  10895. // When tooltip follows mouse, relate the position to the mouse
  10896. if (this.followPointer && mouseEvent) {
  10897. if (mouseEvent.chartX === undefined) {
  10898. mouseEvent = chart.pointer.normalize(mouseEvent);
  10899. }
  10900. ret = [
  10901. mouseEvent.chartX - chart.plotLeft,
  10902. mouseEvent.chartY - plotTop
  10903. ];
  10904. }
  10905. // When shared, use the average position
  10906. if (!ret) {
  10907. each(points, function(point) {
  10908. yAxis = point.series.yAxis;
  10909. xAxis = point.series.xAxis;
  10910. plotX += point.plotX + (!inverted && xAxis ? xAxis.left - plotLeft : 0);
  10911. plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +
  10912. (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
  10913. });
  10914. plotX /= points.length;
  10915. plotY /= points.length;
  10916. ret = [
  10917. inverted ? chart.plotWidth - plotY : plotX,
  10918. this.shared && !inverted && points.length > 1 && mouseEvent ?
  10919. mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424)
  10920. inverted ? chart.plotHeight - plotX : plotY
  10921. ];
  10922. }
  10923. return map(ret, Math.round);
  10924. },
  10925. /**
  10926. * Place the tooltip in a chart without spilling over
  10927. * and not covering the point it self.
  10928. */
  10929. getPosition: function(boxWidth, boxHeight, point) {
  10930. var chart = this.chart,
  10931. distance = this.distance,
  10932. ret = {},
  10933. h = point.h || 0, // #4117
  10934. swapped,
  10935. first = ['y', chart.chartHeight, boxHeight,
  10936. point.plotY + chart.plotTop, chart.plotTop,
  10937. chart.plotTop + chart.plotHeight
  10938. ],
  10939. second = ['x', chart.chartWidth, boxWidth,
  10940. point.plotX + chart.plotLeft, chart.plotLeft,
  10941. chart.plotLeft + chart.plotWidth
  10942. ],
  10943. // The far side is right or bottom
  10944. preferFarSide = !this.followPointer && pick(point.ttBelow, !chart.inverted === !!point.negative), // #4984
  10945. /**
  10946. * Handle the preferred dimension. When the preferred dimension is tooltip
  10947. * on top or bottom of the point, it will look for space there.
  10948. */
  10949. firstDimension = function(dim, outerSize, innerSize, point, min, max) {
  10950. var roomLeft = innerSize < point - distance,
  10951. roomRight = point + distance + innerSize < outerSize,
  10952. alignedLeft = point - distance - innerSize,
  10953. alignedRight = point + distance;
  10954. if (preferFarSide && roomRight) {
  10955. ret[dim] = alignedRight;
  10956. } else if (!preferFarSide && roomLeft) {
  10957. ret[dim] = alignedLeft;
  10958. } else if (roomLeft) {
  10959. ret[dim] = Math.min(max - innerSize, alignedLeft - h < 0 ? alignedLeft : alignedLeft - h);
  10960. } else if (roomRight) {
  10961. ret[dim] = Math.max(
  10962. min,
  10963. alignedRight + h + innerSize > outerSize ?
  10964. alignedRight :
  10965. alignedRight + h
  10966. );
  10967. } else {
  10968. return false;
  10969. }
  10970. },
  10971. /**
  10972. * Handle the secondary dimension. If the preferred dimension is tooltip
  10973. * on top or bottom of the point, the second dimension is to align the tooltip
  10974. * above the point, trying to align center but allowing left or right
  10975. * align within the chart box.
  10976. */
  10977. secondDimension = function(dim, outerSize, innerSize, point) {
  10978. var retVal;
  10979. // Too close to the edge, return false and swap dimensions
  10980. if (point < distance || point > outerSize - distance) {
  10981. retVal = false;
  10982. // Align left/top
  10983. } else if (point < innerSize / 2) {
  10984. ret[dim] = 1;
  10985. // Align right/bottom
  10986. } else if (point > outerSize - innerSize / 2) {
  10987. ret[dim] = outerSize - innerSize - 2;
  10988. // Align center
  10989. } else {
  10990. ret[dim] = point - innerSize / 2;
  10991. }
  10992. return retVal;
  10993. },
  10994. /**
  10995. * Swap the dimensions
  10996. */
  10997. swap = function(count) {
  10998. var temp = first;
  10999. first = second;
  11000. second = temp;
  11001. swapped = count;
  11002. },
  11003. run = function() {
  11004. if (firstDimension.apply(0, first) !== false) {
  11005. if (secondDimension.apply(0, second) === false && !swapped) {
  11006. swap(true);
  11007. run();
  11008. }
  11009. } else if (!swapped) {
  11010. swap(true);
  11011. run();
  11012. } else {
  11013. ret.x = ret.y = 0;
  11014. }
  11015. };
  11016. // Under these conditions, prefer the tooltip on the side of the point
  11017. if (chart.inverted || this.len > 1) {
  11018. swap();
  11019. }
  11020. run();
  11021. return ret;
  11022. },
  11023. /**
  11024. * In case no user defined formatter is given, this will be used. Note that the context
  11025. * here is an object holding point, series, x, y etc.
  11026. *
  11027. * @returns {String|Array<String>}
  11028. */
  11029. defaultFormatter: function(tooltip) {
  11030. var items = this.points || splat(this),
  11031. s;
  11032. // Build the header
  11033. s = [tooltip.tooltipFooterHeaderFormatter(items[0])];
  11034. // build the values
  11035. s = s.concat(tooltip.bodyFormatter(items));
  11036. // footer
  11037. s.push(tooltip.tooltipFooterHeaderFormatter(items[0], true));
  11038. return s;
  11039. },
  11040. /**
  11041. * Refresh the tooltip's text and position.
  11042. * @param {Object|Array} pointOrPoints Rither a point or an array of points
  11043. */
  11044. refresh: function(pointOrPoints, mouseEvent) {
  11045. var tooltip = this,
  11046. label,
  11047. options = tooltip.options,
  11048. x,
  11049. y,
  11050. point = pointOrPoints,
  11051. anchor,
  11052. textConfig = {},
  11053. text,
  11054. pointConfig = [],
  11055. formatter = options.formatter || tooltip.defaultFormatter,
  11056. shared = tooltip.shared,
  11057. currentSeries;
  11058. clearTimeout(this.hideTimer);
  11059. // get the reference point coordinates (pie charts use tooltipPos)
  11060. tooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer;
  11061. anchor = tooltip.getAnchor(point, mouseEvent);
  11062. x = anchor[0];
  11063. y = anchor[1];
  11064. // shared tooltip, array is sent over
  11065. if (shared && !(point.series && point.series.noSharedTooltip)) {
  11066. each(point, function(item) {
  11067. item.setState('hover');
  11068. pointConfig.push(item.getLabelConfig());
  11069. });
  11070. textConfig = {
  11071. x: point[0].category,
  11072. y: point[0].y
  11073. };
  11074. textConfig.points = pointConfig;
  11075. point = point[0];
  11076. // single point tooltip
  11077. } else {
  11078. textConfig = point.getLabelConfig();
  11079. }
  11080. this.len = pointConfig.length; // #6128
  11081. text = formatter.call(textConfig, tooltip);
  11082. // register the current series
  11083. currentSeries = point.series;
  11084. this.distance = pick(currentSeries.tooltipOptions.distance, 16);
  11085. // update the inner HTML
  11086. if (text === false) {
  11087. this.hide();
  11088. } else {
  11089. label = tooltip.getLabel();
  11090. // show it
  11091. if (tooltip.isHidden) {
  11092. label.attr({
  11093. opacity: 1
  11094. }).show();
  11095. }
  11096. // update text
  11097. if (tooltip.split) {
  11098. this.renderSplit(text, pointOrPoints);
  11099. } else {
  11100. // Prevent the tooltip from flowing over the chart box (#6659)
  11101. if (!options.style.width) {
  11102. label.css({
  11103. width: this.chart.spacingBox.width
  11104. });
  11105. }
  11106. label.attr({
  11107. text: text && text.join ? text.join('') : text
  11108. });
  11109. // Set the stroke color of the box to reflect the point
  11110. label.removeClass(/highcharts-color-[\d]+/g)
  11111. .addClass('highcharts-color-' + pick(point.colorIndex, currentSeries.colorIndex));
  11112. label.attr({
  11113. stroke: options.borderColor || point.color || currentSeries.color || '#666666'
  11114. });
  11115. tooltip.updatePosition({
  11116. plotX: x,
  11117. plotY: y,
  11118. negative: point.negative,
  11119. ttBelow: point.ttBelow,
  11120. h: anchor[2] || 0
  11121. });
  11122. }
  11123. this.isHidden = false;
  11124. }
  11125. },
  11126. /**
  11127. * Render the split tooltip. Loops over each point's text and adds
  11128. * a label next to the point, then uses the distribute function to
  11129. * find best non-overlapping positions.
  11130. */
  11131. renderSplit: function(labels, points) {
  11132. var tooltip = this,
  11133. boxes = [],
  11134. chart = this.chart,
  11135. ren = chart.renderer,
  11136. rightAligned = true,
  11137. options = this.options,
  11138. headerHeight,
  11139. tooltipLabel = this.getLabel();
  11140. // Create the individual labels for header and points, ignore footer
  11141. each(labels.slice(0, points.length + 1), function(str, i) {
  11142. var point = points[i - 1] ||
  11143. // Item 0 is the header. Instead of this, we could also use the crosshair label
  11144. {
  11145. isHeader: true,
  11146. plotX: points[0].plotX
  11147. },
  11148. owner = point.series || tooltip,
  11149. tt = owner.tt,
  11150. series = point.series || {},
  11151. colorClass = 'highcharts-color-' + pick(point.colorIndex, series.colorIndex, 'none'),
  11152. target,
  11153. x,
  11154. bBox,
  11155. boxWidth;
  11156. // Store the tooltip referance on the series
  11157. if (!tt) {
  11158. owner.tt = tt = ren.label(null, null, null, 'callout')
  11159. .addClass('highcharts-tooltip-box ' + colorClass)
  11160. .attr({
  11161. 'padding': options.padding,
  11162. 'r': options.borderRadius,
  11163. 'fill': options.backgroundColor,
  11164. 'stroke': point.color || series.color || '#333333',
  11165. 'stroke-width': options.borderWidth
  11166. })
  11167. .add(tooltipLabel);
  11168. }
  11169. tt.isActive = true;
  11170. tt.attr({
  11171. text: str
  11172. });
  11173. tt.css(options.style);
  11174. // Get X position now, so we can move all to the other side in case of overflow
  11175. bBox = tt.getBBox();
  11176. boxWidth = bBox.width + tt.strokeWidth();
  11177. if (point.isHeader) {
  11178. headerHeight = bBox.height;
  11179. x = Math.max(
  11180. 0, // No left overflow
  11181. Math.min(
  11182. point.plotX + chart.plotLeft - boxWidth / 2,
  11183. chart.chartWidth - boxWidth // No right overflow (#5794)
  11184. )
  11185. );
  11186. } else {
  11187. x = point.plotX + chart.plotLeft - pick(options.distance, 16) -
  11188. boxWidth;
  11189. }
  11190. // If overflow left, we don't use this x in the next loop
  11191. if (x < 0) {
  11192. rightAligned = false;
  11193. }
  11194. // Prepare for distribution
  11195. target = (point.series && point.series.yAxis && point.series.yAxis.pos) + (point.plotY || 0);
  11196. target -= chart.plotTop;
  11197. boxes.push({
  11198. target: point.isHeader ? chart.plotHeight + headerHeight : target,
  11199. rank: point.isHeader ? 1 : 0,
  11200. size: owner.tt.getBBox().height + 1,
  11201. point: point,
  11202. x: x,
  11203. tt: tt
  11204. });
  11205. });
  11206. // Clean previous run (for missing points)
  11207. this.cleanSplit();
  11208. // Distribute and put in place
  11209. H.distribute(boxes, chart.plotHeight + headerHeight);
  11210. each(boxes, function(box) {
  11211. var point = box.point,
  11212. series = point.series;
  11213. // Put the label in place
  11214. box.tt.attr({
  11215. visibility: box.pos === undefined ? 'hidden' : 'inherit',
  11216. x: (rightAligned || point.isHeader ?
  11217. box.x :
  11218. point.plotX + chart.plotLeft + pick(options.distance, 16)),
  11219. y: box.pos + chart.plotTop,
  11220. anchorX: point.isHeader ?
  11221. point.plotX + chart.plotLeft : point.plotX + series.xAxis.pos,
  11222. anchorY: point.isHeader ?
  11223. box.pos + chart.plotTop - 15 : point.plotY + series.yAxis.pos
  11224. });
  11225. });
  11226. },
  11227. /**
  11228. * Find the new position and perform the move
  11229. */
  11230. updatePosition: function(point) {
  11231. var chart = this.chart,
  11232. label = this.getLabel(),
  11233. pos = (this.options.positioner || this.getPosition).call(
  11234. this,
  11235. label.width,
  11236. label.height,
  11237. point
  11238. );
  11239. // do the move
  11240. this.move(
  11241. Math.round(pos.x),
  11242. Math.round(pos.y || 0), // can be undefined (#3977)
  11243. point.plotX + chart.plotLeft,
  11244. point.plotY + chart.plotTop
  11245. );
  11246. },
  11247. /**
  11248. * Get the optimal date format for a point, based on a range.
  11249. * @param {number} range - The time range
  11250. * @param {number|Date} date - The date of the point in question
  11251. * @param {number} startOfWeek - An integer representing the first day of
  11252. * the week, where 0 is Sunday
  11253. * @param {Object} dateTimeLabelFormats - A map of time units to formats
  11254. * @return {string} - the optimal date format for a point
  11255. */
  11256. getDateFormat: function(range, date, startOfWeek, dateTimeLabelFormats) {
  11257. var dateStr = dateFormat('%m-%d %H:%M:%S.%L', date),
  11258. format,
  11259. n,
  11260. blank = '01-01 00:00:00.000',
  11261. strpos = {
  11262. millisecond: 15,
  11263. second: 12,
  11264. minute: 9,
  11265. hour: 6,
  11266. day: 3
  11267. },
  11268. lastN = 'millisecond'; // for sub-millisecond data, #4223
  11269. for (n in timeUnits) {
  11270. // If the range is exactly one week and we're looking at a Sunday/Monday, go for the week format
  11271. if (range === timeUnits.week && +dateFormat('%w', date) === startOfWeek &&
  11272. dateStr.substr(6) === blank.substr(6)) {
  11273. n = 'week';
  11274. break;
  11275. }
  11276. // The first format that is too great for the range
  11277. if (timeUnits[n] > range) {
  11278. n = lastN;
  11279. break;
  11280. }
  11281. // If the point is placed every day at 23:59, we need to show
  11282. // the minutes as well. #2637.
  11283. if (strpos[n] && dateStr.substr(strpos[n]) !== blank.substr(strpos[n])) {
  11284. break;
  11285. }
  11286. // Weeks are outside the hierarchy, only apply them on Mondays/Sundays like in the first condition
  11287. if (n !== 'week') {
  11288. lastN = n;
  11289. }
  11290. }
  11291. if (n) {
  11292. format = dateTimeLabelFormats[n];
  11293. }
  11294. return format;
  11295. },
  11296. /**
  11297. * Get the best X date format based on the closest point range on the axis.
  11298. */
  11299. getXDateFormat: function(point, options, xAxis) {
  11300. var xDateFormat,
  11301. dateTimeLabelFormats = options.dateTimeLabelFormats,
  11302. closestPointRange = xAxis && xAxis.closestPointRange;
  11303. if (closestPointRange) {
  11304. xDateFormat = this.getDateFormat(
  11305. closestPointRange,
  11306. point.x,
  11307. xAxis.options.startOfWeek,
  11308. dateTimeLabelFormats
  11309. );
  11310. } else {
  11311. xDateFormat = dateTimeLabelFormats.day;
  11312. }
  11313. return xDateFormat || dateTimeLabelFormats.year; // #2546, 2581
  11314. },
  11315. /**
  11316. * Format the footer/header of the tooltip
  11317. * #3397: abstraction to enable formatting of footer and header
  11318. */
  11319. tooltipFooterHeaderFormatter: function(labelConfig, isFooter) {
  11320. var footOrHead = isFooter ? 'footer' : 'header',
  11321. series = labelConfig.series,
  11322. tooltipOptions = series.tooltipOptions,
  11323. xDateFormat = tooltipOptions.xDateFormat,
  11324. xAxis = series.xAxis,
  11325. isDateTime = xAxis && xAxis.options.type === 'datetime' && isNumber(labelConfig.key),
  11326. formatString = tooltipOptions[footOrHead + 'Format'];
  11327. // Guess the best date format based on the closest point distance (#568, #3418)
  11328. if (isDateTime && !xDateFormat) {
  11329. xDateFormat = this.getXDateFormat(labelConfig, tooltipOptions, xAxis);
  11330. }
  11331. // Insert the footer date format if any
  11332. if (isDateTime && xDateFormat) {
  11333. formatString = formatString.replace('{point.key}', '{point.key:' + xDateFormat + '}');
  11334. }
  11335. return format(formatString, {
  11336. point: labelConfig,
  11337. series: series
  11338. });
  11339. },
  11340. /**
  11341. * Build the body (lines) of the tooltip by iterating over the items and returning one entry for each item,
  11342. * abstracting this functionality allows to easily overwrite and extend it.
  11343. */
  11344. bodyFormatter: function(items) {
  11345. return map(items, function(item) {
  11346. var tooltipOptions = item.series.tooltipOptions;
  11347. return (tooltipOptions.pointFormatter || item.point.tooltipFormatter)
  11348. .call(item.point, tooltipOptions.pointFormat);
  11349. });
  11350. }
  11351. };
  11352. }(Highcharts));
  11353. (function(H) {
  11354. /**
  11355. * (c) 2010-2017 Torstein Honsi
  11356. *
  11357. * License: www.highcharts.com/license
  11358. */
  11359. var addEvent = H.addEvent,
  11360. attr = H.attr,
  11361. charts = H.charts,
  11362. color = H.color,
  11363. css = H.css,
  11364. defined = H.defined,
  11365. doc = H.doc,
  11366. each = H.each,
  11367. extend = H.extend,
  11368. fireEvent = H.fireEvent,
  11369. offset = H.offset,
  11370. pick = H.pick,
  11371. removeEvent = H.removeEvent,
  11372. splat = H.splat,
  11373. Tooltip = H.Tooltip,
  11374. win = H.win;
  11375. /**
  11376. * The mouse tracker object. All methods starting with "on" are primary DOM
  11377. * event handlers. Subsequent methods should be named differently from what they
  11378. * are doing.
  11379. *
  11380. * @constructor Pointer
  11381. * @param {Object} chart The Chart instance
  11382. * @param {Object} options The root options object
  11383. */
  11384. H.Pointer = function(chart, options) {
  11385. this.init(chart, options);
  11386. };
  11387. H.Pointer.prototype = {
  11388. /**
  11389. * Initialize Pointer
  11390. */
  11391. init: function(chart, options) {
  11392. // Store references
  11393. this.options = options;
  11394. this.chart = chart;
  11395. // Do we need to handle click on a touch device?
  11396. this.runChartClick = options.chart.events && !!options.chart.events.click;
  11397. this.pinchDown = [];
  11398. this.lastValidTouch = {};
  11399. if (Tooltip && options.tooltip.enabled) {
  11400. chart.tooltip = new Tooltip(chart, options.tooltip);
  11401. this.followTouchMove = pick(options.tooltip.followTouchMove, true);
  11402. }
  11403. this.setDOMEvents();
  11404. },
  11405. /**
  11406. * Resolve the zoomType option, this is reset on all touch start and mouse
  11407. * down events.
  11408. */
  11409. zoomOption: function(e) {
  11410. var chart = this.chart,
  11411. options = chart.options.chart,
  11412. zoomType = options.zoomType || '',
  11413. inverted = chart.inverted,
  11414. zoomX,
  11415. zoomY;
  11416. // Look for the pinchType option
  11417. if (/touch/.test(e.type)) {
  11418. zoomType = pick(options.pinchType, zoomType);
  11419. }
  11420. this.zoomX = zoomX = /x/.test(zoomType);
  11421. this.zoomY = zoomY = /y/.test(zoomType);
  11422. this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
  11423. this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
  11424. this.hasZoom = zoomX || zoomY;
  11425. },
  11426. /**
  11427. * @typedef {Object} PointerEvent
  11428. * A native browser mouse or touch event, extended with position
  11429. * information relative to the {@link Chart.container}.
  11430. * @property {Number} chartX
  11431. * The X coordinate of the pointer interaction relative to the
  11432. * chart.
  11433. * @property {Number} chartY
  11434. * The Y coordinate of the pointer interaction relative to the
  11435. * chart.
  11436. *
  11437. */
  11438. /**
  11439. * Add crossbrowser support for chartX and chartY.
  11440. *
  11441. * @param {Object} e
  11442. * The event object in standard browsers.
  11443. *
  11444. * @return {PointerEvent}
  11445. * A browser event with extended properties `chartX` and `chartY`
  11446. */
  11447. normalize: function(e, chartPosition) {
  11448. var chartX,
  11449. chartY,
  11450. ePos;
  11451. // IE normalizing
  11452. e = e || win.event;
  11453. if (!e.target) {
  11454. e.target = e.srcElement;
  11455. }
  11456. // iOS (#2757)
  11457. ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e;
  11458. // Get mouse position
  11459. if (!chartPosition) {
  11460. this.chartPosition = chartPosition = offset(this.chart.container);
  11461. }
  11462. // chartX and chartY
  11463. if (ePos.pageX === undefined) { // IE < 9. #886.
  11464. chartX = Math.max(e.x, e.clientX - chartPosition.left); // #2005, #2129: the second case is
  11465. // for IE10 quirks mode within framesets
  11466. chartY = e.y;
  11467. } else {
  11468. chartX = ePos.pageX - chartPosition.left;
  11469. chartY = ePos.pageY - chartPosition.top;
  11470. }
  11471. return extend(e, {
  11472. chartX: Math.round(chartX),
  11473. chartY: Math.round(chartY)
  11474. });
  11475. },
  11476. /**
  11477. * Get the click position in terms of axis values.
  11478. *
  11479. * @param {Object} e A pointer event
  11480. */
  11481. getCoordinates: function(e) {
  11482. var coordinates = {
  11483. xAxis: [],
  11484. yAxis: []
  11485. };
  11486. each(this.chart.axes, function(axis) {
  11487. coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
  11488. axis: axis,
  11489. value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
  11490. });
  11491. });
  11492. return coordinates;
  11493. },
  11494. /**
  11495. * Collects the points closest to a mouseEvent
  11496. * @param {Array} series Array of series to gather points from
  11497. * @param {Boolean} shared True if shared tooltip, otherwise false
  11498. * @param {Object} e Mouse event which possess a position to compare against
  11499. * @return {Array} KDPoints sorted by distance
  11500. */
  11501. getKDPoints: function(series, shared, e) {
  11502. var kdpoints = [],
  11503. noSharedTooltip,
  11504. directTouch,
  11505. kdpointT,
  11506. i;
  11507. // Find nearest points on all series
  11508. each(series, function(s) {
  11509. // Skip hidden series
  11510. noSharedTooltip = s.noSharedTooltip && shared;
  11511. directTouch = !shared && s.directTouch;
  11512. if (s.visible && !directTouch && pick(s.options.enableMouseTracking, true)) { // #3821
  11513. // #3828
  11514. kdpointT = s.searchPoint(
  11515. e, !noSharedTooltip && s.options.findNearestPointBy.indexOf('y') < 0
  11516. );
  11517. if (kdpointT && kdpointT.series) { // Point.series becomes null when reset and before redraw (#5197)
  11518. kdpoints.push(kdpointT);
  11519. }
  11520. }
  11521. });
  11522. // Sort kdpoints by distance to mouse pointer
  11523. kdpoints.sort(function(p1, p2) {
  11524. var isCloserX = p1.distX - p2.distX,
  11525. isCloser = p1.dist - p2.dist,
  11526. isAbove =
  11527. (p2.series.group && p2.series.group.zIndex) -
  11528. (p1.series.group && p1.series.group.zIndex),
  11529. result;
  11530. // We have two points which are not in the same place on xAxis and shared tooltip:
  11531. if (isCloserX !== 0 && shared) { // #5721
  11532. result = isCloserX;
  11533. // Points are not exactly in the same place on x/yAxis:
  11534. } else if (isCloser !== 0) {
  11535. result = isCloser;
  11536. // The same xAxis and yAxis position, sort by z-index:
  11537. } else if (isAbove !== 0) {
  11538. result = isAbove;
  11539. // The same zIndex, sort by array index:
  11540. } else {
  11541. result = p1.series.index > p2.series.index ? -1 : 1;
  11542. }
  11543. return result;
  11544. });
  11545. // Remove points with different x-positions, required for shared tooltip and crosshairs (#4645):
  11546. if (shared && kdpoints[0] && !kdpoints[0].series.noSharedTooltip) {
  11547. i = kdpoints.length;
  11548. while (i--) {
  11549. if (kdpoints[i].x !== kdpoints[0].x || kdpoints[i].series.noSharedTooltip) {
  11550. kdpoints.splice(i, 1);
  11551. }
  11552. }
  11553. }
  11554. return kdpoints;
  11555. },
  11556. getPointFromEvent: function(e) {
  11557. var target = e.target,
  11558. point;
  11559. while (target && !point) {
  11560. point = target.point;
  11561. target = target.parentNode;
  11562. }
  11563. return point;
  11564. },
  11565. getChartCoordinatesFromPoint: function(point, inverted) {
  11566. var series = point.series,
  11567. xAxis = series.xAxis,
  11568. yAxis = series.yAxis;
  11569. if (xAxis && yAxis) {
  11570. return inverted ? {
  11571. chartX: xAxis.len + xAxis.pos - point.clientX,
  11572. chartY: yAxis.len + yAxis.pos - point.plotY
  11573. } : {
  11574. chartX: point.clientX + xAxis.pos,
  11575. chartY: point.plotY + yAxis.pos
  11576. };
  11577. }
  11578. },
  11579. /**
  11580. * Calculates what is the current hovered point/points and series.
  11581. *
  11582. * @private
  11583. *
  11584. * @param {undefined|Point} existingHoverPoint
  11585. * The point currrently beeing hovered.
  11586. * @param {undefined|Series} existingHoverSeries
  11587. * The series currently beeing hovered.
  11588. * @param {Array<.Series>} series
  11589. * All the series in the chart.
  11590. * @param {boolean} isDirectTouch
  11591. * Is the pointer directly hovering the point.
  11592. * @param {boolean} shared
  11593. * Whether it is a shared tooltip or not.
  11594. * @param {object} coordinates
  11595. * Chart coordinates of the pointer.
  11596. * @param {number} coordinates.chartX
  11597. * @param {number} coordinates.chartY
  11598. *
  11599. * @return {object}
  11600. * Object containing resulting hover data.
  11601. */
  11602. getHoverData: function(
  11603. existingHoverPoint,
  11604. existingHoverSeries,
  11605. series,
  11606. isDirectTouch,
  11607. shared,
  11608. coordinates
  11609. ) {
  11610. var hoverPoint = existingHoverPoint,
  11611. hoverSeries = existingHoverSeries,
  11612. searchSeries = shared ? series : [hoverSeries],
  11613. useExisting = !!(isDirectTouch && existingHoverPoint),
  11614. notSticky = hoverSeries && !hoverSeries.stickyTracking,
  11615. isHoverPoint = function(point, i) {
  11616. return i === 0;
  11617. },
  11618. hoverPoints;
  11619. // If there is a hoverPoint and its series requires direct touch (like
  11620. // columns, #3899), or we're on a noSharedTooltip series among shared
  11621. // tooltip series (#4546), use the existing hoverPoint.
  11622. if (useExisting) {
  11623. isHoverPoint = function(p) {
  11624. return p === existingHoverPoint;
  11625. };
  11626. } else if (notSticky) {
  11627. isHoverPoint = function(p) {
  11628. return p.series === hoverSeries;
  11629. };
  11630. } else {
  11631. // Avoid series with stickyTracking false
  11632. searchSeries = H.grep(series, function(s) {
  11633. return s.stickyTracking;
  11634. });
  11635. }
  11636. hoverPoints = (useExisting && !shared) ?
  11637. // Non-shared tooltips with directTouch don't use the k-d-tree
  11638. [existingHoverPoint] :
  11639. this.getKDPoints(searchSeries, shared, coordinates);
  11640. hoverPoint = H.find(hoverPoints, isHoverPoint);
  11641. hoverSeries = hoverPoint && hoverPoint.series;
  11642. // In this case we could only look for the hoverPoint in series with
  11643. // stickyTracking, but we should still include all series in the shared
  11644. // tooltip.
  11645. if (!useExisting && !notSticky && shared) {
  11646. hoverPoints = this.getKDPoints(series, shared, coordinates);
  11647. }
  11648. // Keep the order of series in tooltip
  11649. // Must be done after assigning of hoverPoint
  11650. hoverPoints.sort(function(p1, p2) {
  11651. return p1.series.index - p2.series.index;
  11652. });
  11653. return {
  11654. hoverPoint: hoverPoint,
  11655. hoverSeries: hoverSeries,
  11656. hoverPoints: hoverPoints
  11657. };
  11658. },
  11659. /**
  11660. * With line type charts with a single tracker, get the point closest to the mouse.
  11661. * Run Point.onMouseOver and display tooltip for the point or points.
  11662. */
  11663. runPointActions: function(e, p) {
  11664. var pointer = this,
  11665. chart = pointer.chart,
  11666. series = chart.series,
  11667. tooltip = chart.tooltip,
  11668. shared = tooltip ? tooltip.shared : false,
  11669. hoverPoint = p || chart.hoverPoint,
  11670. hoverSeries = hoverPoint && hoverPoint.series || chart.hoverSeries,
  11671. // onMouseOver or already hovering a series with directTouch
  11672. isDirectTouch = !!p || (
  11673. (hoverSeries && hoverSeries.directTouch) &&
  11674. pointer.isDirectTouch
  11675. ),
  11676. hoverData = this.getHoverData(
  11677. hoverPoint,
  11678. hoverSeries,
  11679. series,
  11680. isDirectTouch,
  11681. shared,
  11682. e
  11683. ),
  11684. useSharedTooltip,
  11685. followPointer,
  11686. anchor,
  11687. points;
  11688. // Update variables from hoverData.
  11689. hoverPoint = hoverData.hoverPoint;
  11690. hoverSeries = hoverData.hoverSeries;
  11691. followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer;
  11692. useSharedTooltip = (
  11693. shared &&
  11694. hoverPoint &&
  11695. !hoverPoint.series.noSharedTooltip
  11696. );
  11697. points = (useSharedTooltip ?
  11698. hoverData.hoverPoints :
  11699. (hoverPoint ? [hoverPoint] : [])
  11700. );
  11701. // Refresh tooltip for kdpoint if new hover point or tooltip was hidden
  11702. // #3926, #4200
  11703. if (
  11704. hoverPoint &&
  11705. // !(hoverSeries && hoverSeries.directTouch) &&
  11706. (hoverPoint !== chart.hoverPoint || (tooltip && tooltip.isHidden))
  11707. ) {
  11708. each(chart.hoverPoints || [], function(p) {
  11709. if (H.inArray(p, points) === -1) {
  11710. p.setState();
  11711. }
  11712. });
  11713. // Do mouseover on all points (#3919, #3985, #4410, #5622)
  11714. each(points || [], function(p) {
  11715. p.setState('hover');
  11716. });
  11717. // set normal state to previous series
  11718. if (chart.hoverSeries !== hoverSeries) {
  11719. hoverSeries.onMouseOver();
  11720. }
  11721. // If tracking is on series in stead of on each point,
  11722. // fire mouseOver on hover point. // #4448
  11723. if (chart.hoverPoint) {
  11724. chart.hoverPoint.firePointEvent('mouseOut');
  11725. }
  11726. hoverPoint.firePointEvent('mouseOver');
  11727. chart.hoverPoints = points;
  11728. chart.hoverPoint = hoverPoint;
  11729. // Draw tooltip if necessary
  11730. if (tooltip) {
  11731. tooltip.refresh(useSharedTooltip ? points : hoverPoint, e);
  11732. }
  11733. // Update positions (regardless of kdpoint or hoverPoint)
  11734. } else if (followPointer && tooltip && !tooltip.isHidden) {
  11735. anchor = tooltip.getAnchor([{}], e);
  11736. tooltip.updatePosition({
  11737. plotX: anchor[0],
  11738. plotY: anchor[1]
  11739. });
  11740. }
  11741. // Start the event listener to pick up the tooltip and crosshairs
  11742. if (!pointer.unDocMouseMove) {
  11743. pointer.unDocMouseMove = addEvent(doc, 'mousemove', function(e) {
  11744. var chart = charts[H.hoverChartIndex];
  11745. if (chart) {
  11746. chart.pointer.onDocumentMouseMove(e);
  11747. }
  11748. });
  11749. }
  11750. // Issues related to crosshair #4927, #5269 #5066, #5658
  11751. each(chart.axes, function drawAxisCrosshair(axis) {
  11752. var snap = pick(axis.crosshair.snap, true);
  11753. if (!snap) {
  11754. axis.drawCrosshair(e);
  11755. // Axis has snapping crosshairs, and one of the hover points belongs
  11756. // to axis
  11757. } else if (H.find(points, function(p) {
  11758. return p.series[axis.coll] === axis;
  11759. })) {
  11760. axis.drawCrosshair(e, hoverPoint);
  11761. // Axis has snapping crosshairs, but no hover point belongs to axis
  11762. } else {
  11763. axis.hideCrosshair();
  11764. }
  11765. });
  11766. },
  11767. /**
  11768. * Reset the tracking by hiding the tooltip, the hover series state and the
  11769. * hover point
  11770. *
  11771. * @param allowMove {Boolean}
  11772. * Instead of destroying the tooltip altogether, allow moving it if
  11773. * possible
  11774. */
  11775. reset: function(allowMove, delay) {
  11776. var pointer = this,
  11777. chart = pointer.chart,
  11778. hoverSeries = chart.hoverSeries,
  11779. hoverPoint = chart.hoverPoint,
  11780. hoverPoints = chart.hoverPoints,
  11781. tooltip = chart.tooltip,
  11782. tooltipPoints = tooltip && tooltip.shared ? hoverPoints : hoverPoint;
  11783. // Check if the points have moved outside the plot area (#1003, #4736, #5101)
  11784. if (allowMove && tooltipPoints) {
  11785. each(splat(tooltipPoints), function(point) {
  11786. if (point.series.isCartesian && point.plotX === undefined) {
  11787. allowMove = false;
  11788. }
  11789. });
  11790. }
  11791. // Just move the tooltip, #349
  11792. if (allowMove) {
  11793. if (tooltip && tooltipPoints) {
  11794. tooltip.refresh(tooltipPoints);
  11795. if (hoverPoint) { // #2500
  11796. hoverPoint.setState(hoverPoint.state, true);
  11797. each(chart.axes, function(axis) {
  11798. if (axis.crosshair) {
  11799. axis.drawCrosshair(null, hoverPoint);
  11800. }
  11801. });
  11802. }
  11803. }
  11804. // Full reset
  11805. } else {
  11806. if (hoverPoint) {
  11807. hoverPoint.onMouseOut();
  11808. }
  11809. if (hoverPoints) {
  11810. each(hoverPoints, function(point) {
  11811. point.setState();
  11812. });
  11813. }
  11814. if (hoverSeries) {
  11815. hoverSeries.onMouseOut();
  11816. }
  11817. if (tooltip) {
  11818. tooltip.hide(delay);
  11819. }
  11820. if (pointer.unDocMouseMove) {
  11821. pointer.unDocMouseMove = pointer.unDocMouseMove();
  11822. }
  11823. // Remove crosshairs
  11824. each(chart.axes, function(axis) {
  11825. axis.hideCrosshair();
  11826. });
  11827. pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null;
  11828. }
  11829. },
  11830. /**
  11831. * Scale series groups to a certain scale and translation
  11832. */
  11833. scaleGroups: function(attribs, clip) {
  11834. var chart = this.chart,
  11835. seriesAttribs;
  11836. // Scale each series
  11837. each(chart.series, function(series) {
  11838. seriesAttribs = attribs || series.getPlotBox(); // #1701
  11839. if (series.xAxis && series.xAxis.zoomEnabled && series.group) {
  11840. series.group.attr(seriesAttribs);
  11841. if (series.markerGroup) {
  11842. series.markerGroup.attr(seriesAttribs);
  11843. series.markerGroup.clip(clip ? chart.clipRect : null);
  11844. }
  11845. if (series.dataLabelsGroup) {
  11846. series.dataLabelsGroup.attr(seriesAttribs);
  11847. }
  11848. }
  11849. });
  11850. // Clip
  11851. chart.clipRect.attr(clip || chart.clipBox);
  11852. },
  11853. /**
  11854. * Start a drag operation
  11855. */
  11856. dragStart: function(e) {
  11857. var chart = this.chart;
  11858. // Record the start position
  11859. chart.mouseIsDown = e.type;
  11860. chart.cancelClick = false;
  11861. chart.mouseDownX = this.mouseDownX = e.chartX;
  11862. chart.mouseDownY = this.mouseDownY = e.chartY;
  11863. },
  11864. /**
  11865. * Perform a drag operation in response to a mousemove event while the mouse is down
  11866. */
  11867. drag: function(e) {
  11868. var chart = this.chart,
  11869. chartOptions = chart.options.chart,
  11870. chartX = e.chartX,
  11871. chartY = e.chartY,
  11872. zoomHor = this.zoomHor,
  11873. zoomVert = this.zoomVert,
  11874. plotLeft = chart.plotLeft,
  11875. plotTop = chart.plotTop,
  11876. plotWidth = chart.plotWidth,
  11877. plotHeight = chart.plotHeight,
  11878. clickedInside,
  11879. size,
  11880. selectionMarker = this.selectionMarker,
  11881. mouseDownX = this.mouseDownX,
  11882. mouseDownY = this.mouseDownY,
  11883. panKey = chartOptions.panKey && e[chartOptions.panKey + 'Key'];
  11884. // If the device supports both touch and mouse (like IE11), and we are touch-dragging
  11885. // inside the plot area, don't handle the mouse event. #4339.
  11886. if (selectionMarker && selectionMarker.touch) {
  11887. return;
  11888. }
  11889. // If the mouse is outside the plot area, adjust to cooordinates
  11890. // inside to prevent the selection marker from going outside
  11891. if (chartX < plotLeft) {
  11892. chartX = plotLeft;
  11893. } else if (chartX > plotLeft + plotWidth) {
  11894. chartX = plotLeft + plotWidth;
  11895. }
  11896. if (chartY < plotTop) {
  11897. chartY = plotTop;
  11898. } else if (chartY > plotTop + plotHeight) {
  11899. chartY = plotTop + plotHeight;
  11900. }
  11901. // determine if the mouse has moved more than 10px
  11902. this.hasDragged = Math.sqrt(
  11903. Math.pow(mouseDownX - chartX, 2) +
  11904. Math.pow(mouseDownY - chartY, 2)
  11905. );
  11906. if (this.hasDragged > 10) {
  11907. clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
  11908. // make a selection
  11909. if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside && !panKey) {
  11910. if (!selectionMarker) {
  11911. this.selectionMarker = selectionMarker = chart.renderer.rect(
  11912. plotLeft,
  11913. plotTop,
  11914. zoomHor ? 1 : plotWidth,
  11915. zoomVert ? 1 : plotHeight,
  11916. 0
  11917. )
  11918. .attr({
  11919. fill: chartOptions.selectionMarkerFill || color('#335cad').setOpacity(0.25).get(),
  11920. 'class': 'highcharts-selection-marker',
  11921. 'zIndex': 7
  11922. })
  11923. .add();
  11924. }
  11925. }
  11926. // adjust the width of the selection marker
  11927. if (selectionMarker && zoomHor) {
  11928. size = chartX - mouseDownX;
  11929. selectionMarker.attr({
  11930. width: Math.abs(size),
  11931. x: (size > 0 ? 0 : size) + mouseDownX
  11932. });
  11933. }
  11934. // adjust the height of the selection marker
  11935. if (selectionMarker && zoomVert) {
  11936. size = chartY - mouseDownY;
  11937. selectionMarker.attr({
  11938. height: Math.abs(size),
  11939. y: (size > 0 ? 0 : size) + mouseDownY
  11940. });
  11941. }
  11942. // panning
  11943. if (clickedInside && !selectionMarker && chartOptions.panning) {
  11944. chart.pan(e, chartOptions.panning);
  11945. }
  11946. }
  11947. },
  11948. /**
  11949. * On mouse up or touch end across the entire document, drop the selection.
  11950. */
  11951. drop: function(e) {
  11952. var pointer = this,
  11953. chart = this.chart,
  11954. hasPinched = this.hasPinched;
  11955. if (this.selectionMarker) {
  11956. var selectionData = {
  11957. originalEvent: e, // #4890
  11958. xAxis: [],
  11959. yAxis: []
  11960. },
  11961. selectionBox = this.selectionMarker,
  11962. selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x,
  11963. selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y,
  11964. selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width,
  11965. selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height,
  11966. runZoom;
  11967. // a selection has been made
  11968. if (this.hasDragged || hasPinched) {
  11969. // record each axis' min and max
  11970. each(chart.axes, function(axis) {
  11971. if (axis.zoomEnabled && defined(axis.min) && (hasPinched || pointer[{
  11972. xAxis: 'zoomX',
  11973. yAxis: 'zoomY'
  11974. }[axis.coll]])) { // #859, #3569
  11975. var horiz = axis.horiz,
  11976. minPixelPadding = e.type === 'touchend' ? axis.minPixelPadding : 0, // #1207, #3075
  11977. selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop) + minPixelPadding),
  11978. selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight) - minPixelPadding);
  11979. selectionData[axis.coll].push({
  11980. axis: axis,
  11981. min: Math.min(selectionMin, selectionMax), // for reversed axes
  11982. max: Math.max(selectionMin, selectionMax)
  11983. });
  11984. runZoom = true;
  11985. }
  11986. });
  11987. if (runZoom) {
  11988. fireEvent(chart, 'selection', selectionData, function(args) {
  11989. chart.zoom(extend(args, hasPinched ? {
  11990. animation: false
  11991. } : null));
  11992. });
  11993. }
  11994. }
  11995. this.selectionMarker = this.selectionMarker.destroy();
  11996. // Reset scaling preview
  11997. if (hasPinched) {
  11998. this.scaleGroups();
  11999. }
  12000. }
  12001. // Reset all
  12002. if (chart) { // it may be destroyed on mouse up - #877
  12003. css(chart.container, {
  12004. cursor: chart._cursor
  12005. });
  12006. chart.cancelClick = this.hasDragged > 10; // #370
  12007. chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
  12008. this.pinchDown = [];
  12009. }
  12010. },
  12011. onContainerMouseDown: function(e) {
  12012. e = this.normalize(e);
  12013. this.zoomOption(e);
  12014. // issue #295, dragging not always working in Firefox
  12015. if (e.preventDefault) {
  12016. e.preventDefault();
  12017. }
  12018. this.dragStart(e);
  12019. },
  12020. onDocumentMouseUp: function(e) {
  12021. if (charts[H.hoverChartIndex]) {
  12022. charts[H.hoverChartIndex].pointer.drop(e);
  12023. }
  12024. },
  12025. /**
  12026. * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
  12027. * Issue #149 workaround. The mouseleave event does not always fire.
  12028. */
  12029. onDocumentMouseMove: function(e) {
  12030. var chart = this.chart,
  12031. chartPosition = this.chartPosition;
  12032. e = this.normalize(e, chartPosition);
  12033. // If we're outside, hide the tooltip
  12034. if (chartPosition && !this.inClass(e.target, 'highcharts-tracker') &&
  12035. !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
  12036. this.reset();
  12037. }
  12038. },
  12039. /**
  12040. * When mouse leaves the container, hide the tooltip.
  12041. */
  12042. onContainerMouseLeave: function(e) {
  12043. var chart = charts[H.hoverChartIndex];
  12044. if (chart && (e.relatedTarget || e.toElement)) { // #4886, MS Touch end fires mouseleave but with no related target
  12045. chart.pointer.reset();
  12046. chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix
  12047. }
  12048. },
  12049. // The mousemove, touchmove and touchstart event handler
  12050. onContainerMouseMove: function(e) {
  12051. var chart = this.chart;
  12052. if (!defined(H.hoverChartIndex) || !charts[H.hoverChartIndex] || !charts[H.hoverChartIndex].mouseIsDown) {
  12053. H.hoverChartIndex = chart.index;
  12054. }
  12055. e = this.normalize(e);
  12056. e.returnValue = false; // #2251, #3224
  12057. if (chart.mouseIsDown === 'mousedown') {
  12058. this.drag(e);
  12059. }
  12060. // Show the tooltip and run mouse over events (#977)
  12061. if ((this.inClass(e.target, 'highcharts-tracker') ||
  12062. chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {
  12063. this.runPointActions(e);
  12064. }
  12065. },
  12066. /**
  12067. * Utility to detect whether an element has, or has a parent with, a specific
  12068. * class name. Used on detection of tracker objects and on deciding whether
  12069. * hovering the tooltip should cause the active series to mouse out.
  12070. */
  12071. inClass: function(element, className) {
  12072. var elemClassName;
  12073. while (element) {
  12074. elemClassName = attr(element, 'class');
  12075. if (elemClassName) {
  12076. if (elemClassName.indexOf(className) !== -1) {
  12077. return true;
  12078. }
  12079. if (elemClassName.indexOf('highcharts-container') !== -1) {
  12080. return false;
  12081. }
  12082. }
  12083. element = element.parentNode;
  12084. }
  12085. },
  12086. onTrackerMouseOut: function(e) {
  12087. var series = this.chart.hoverSeries,
  12088. relatedTarget = e.relatedTarget || e.toElement;
  12089. this.isDirectTouch = false;
  12090. if (series && relatedTarget && !series.stickyTracking &&
  12091. !this.inClass(relatedTarget, 'highcharts-tooltip') &&
  12092. (!this.inClass(relatedTarget, 'highcharts-series-' + series.index) || // #2499, #4465
  12093. !this.inClass(relatedTarget, 'highcharts-tracker') // #5553
  12094. )
  12095. ) {
  12096. series.onMouseOut();
  12097. }
  12098. },
  12099. onContainerClick: function(e) {
  12100. var chart = this.chart,
  12101. hoverPoint = chart.hoverPoint,
  12102. plotLeft = chart.plotLeft,
  12103. plotTop = chart.plotTop;
  12104. e = this.normalize(e);
  12105. if (!chart.cancelClick) {
  12106. // On tracker click, fire the series and point events. #783, #1583
  12107. if (hoverPoint && this.inClass(e.target, 'highcharts-tracker')) {
  12108. // the series click event
  12109. fireEvent(hoverPoint.series, 'click', extend(e, {
  12110. point: hoverPoint
  12111. }));
  12112. // the point click event
  12113. if (chart.hoverPoint) { // it may be destroyed (#1844)
  12114. hoverPoint.firePointEvent('click', e);
  12115. }
  12116. // When clicking outside a tracker, fire a chart event
  12117. } else {
  12118. extend(e, this.getCoordinates(e));
  12119. // fire a click event in the chart
  12120. if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
  12121. fireEvent(chart, 'click', e);
  12122. }
  12123. }
  12124. }
  12125. },
  12126. /**
  12127. * Set the JS DOM events on the container and document. This method should contain
  12128. * a one-to-one assignment between methods and their handlers. Any advanced logic should
  12129. * be moved to the handler reflecting the event's name.
  12130. */
  12131. setDOMEvents: function() {
  12132. var pointer = this,
  12133. container = pointer.chart.container;
  12134. container.onmousedown = function(e) {
  12135. pointer.onContainerMouseDown(e);
  12136. };
  12137. container.onmousemove = function(e) {
  12138. pointer.onContainerMouseMove(e);
  12139. };
  12140. container.onclick = function(e) {
  12141. pointer.onContainerClick(e);
  12142. };
  12143. addEvent(container, 'mouseleave', pointer.onContainerMouseLeave);
  12144. if (H.chartCount === 1) {
  12145. addEvent(doc, 'mouseup', pointer.onDocumentMouseUp);
  12146. }
  12147. if (H.hasTouch) {
  12148. container.ontouchstart = function(e) {
  12149. pointer.onContainerTouchStart(e);
  12150. };
  12151. container.ontouchmove = function(e) {
  12152. pointer.onContainerTouchMove(e);
  12153. };
  12154. if (H.chartCount === 1) {
  12155. addEvent(doc, 'touchend', pointer.onDocumentTouchEnd);
  12156. }
  12157. }
  12158. },
  12159. /**
  12160. * Destroys the Pointer object and disconnects DOM events.
  12161. */
  12162. destroy: function() {
  12163. var pointer = this;
  12164. if (pointer.unDocMouseMove) {
  12165. pointer.unDocMouseMove();
  12166. }
  12167. removeEvent(
  12168. pointer.chart.container,
  12169. 'mouseleave',
  12170. pointer.onContainerMouseLeave
  12171. );
  12172. if (!H.chartCount) {
  12173. removeEvent(doc, 'mouseup', pointer.onDocumentMouseUp);
  12174. removeEvent(doc, 'touchend', pointer.onDocumentTouchEnd);
  12175. }
  12176. // memory and CPU leak
  12177. clearInterval(pointer.tooltipTimeout);
  12178. H.objectEach(pointer, function(val, prop) {
  12179. pointer[prop] = null;
  12180. });
  12181. }
  12182. };
  12183. }(Highcharts));
  12184. (function(H) {
  12185. /**
  12186. * (c) 2010-2017 Torstein Honsi
  12187. *
  12188. * License: www.highcharts.com/license
  12189. */
  12190. var charts = H.charts,
  12191. each = H.each,
  12192. extend = H.extend,
  12193. map = H.map,
  12194. noop = H.noop,
  12195. pick = H.pick,
  12196. Pointer = H.Pointer;
  12197. /* Support for touch devices */
  12198. extend(Pointer.prototype, /** @lends Pointer.prototype */ {
  12199. /**
  12200. * Run translation operations
  12201. */
  12202. pinchTranslate: function(pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
  12203. if (this.zoomHor) {
  12204. this.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
  12205. }
  12206. if (this.zoomVert) {
  12207. this.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
  12208. }
  12209. },
  12210. /**
  12211. * Run translation operations for each direction (horizontal and vertical) independently
  12212. */
  12213. pinchTranslateDirection: function(horiz, pinchDown, touches, transform,
  12214. selectionMarker, clip, lastValidTouch, forcedScale) {
  12215. var chart = this.chart,
  12216. xy = horiz ? 'x' : 'y',
  12217. XY = horiz ? 'X' : 'Y',
  12218. sChartXY = 'chart' + XY,
  12219. wh = horiz ? 'width' : 'height',
  12220. plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],
  12221. selectionWH,
  12222. selectionXY,
  12223. clipXY,
  12224. scale = forcedScale || 1,
  12225. inverted = chart.inverted,
  12226. bounds = chart.bounds[horiz ? 'h' : 'v'],
  12227. singleTouch = pinchDown.length === 1,
  12228. touch0Start = pinchDown[0][sChartXY],
  12229. touch0Now = touches[0][sChartXY],
  12230. touch1Start = !singleTouch && pinchDown[1][sChartXY],
  12231. touch1Now = !singleTouch && touches[1][sChartXY],
  12232. outOfBounds,
  12233. transformScale,
  12234. scaleKey,
  12235. setScale = function() {
  12236. // Don't zoom if fingers are too close on this axis
  12237. if (!singleTouch && Math.abs(touch0Start - touch1Start) > 20) {
  12238. scale = forcedScale || Math.abs(touch0Now - touch1Now) / Math.abs(touch0Start - touch1Start);
  12239. }
  12240. clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
  12241. selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale;
  12242. };
  12243. // Set the scale, first pass
  12244. setScale();
  12245. selectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not
  12246. // Out of bounds
  12247. if (selectionXY < bounds.min) {
  12248. selectionXY = bounds.min;
  12249. outOfBounds = true;
  12250. } else if (selectionXY + selectionWH > bounds.max) {
  12251. selectionXY = bounds.max - selectionWH;
  12252. outOfBounds = true;
  12253. }
  12254. // Is the chart dragged off its bounds, determined by dataMin and dataMax?
  12255. if (outOfBounds) {
  12256. // Modify the touchNow position in order to create an elastic drag movement. This indicates
  12257. // to the user that the chart is responsive but can't be dragged further.
  12258. touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
  12259. if (!singleTouch) {
  12260. touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
  12261. }
  12262. // Set the scale, second pass to adapt to the modified touchNow positions
  12263. setScale();
  12264. } else {
  12265. lastValidTouch[xy] = [touch0Now, touch1Now];
  12266. }
  12267. // Set geometry for clipping, selection and transformation
  12268. if (!inverted) {
  12269. clip[xy] = clipXY - plotLeftTop;
  12270. clip[wh] = selectionWH;
  12271. }
  12272. scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
  12273. transformScale = inverted ? 1 / scale : scale;
  12274. selectionMarker[wh] = selectionWH;
  12275. selectionMarker[xy] = selectionXY;
  12276. transform[scaleKey] = scale;
  12277. transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start));
  12278. },
  12279. /**
  12280. * Handle touch events with two touches
  12281. */
  12282. pinch: function(e) {
  12283. var self = this,
  12284. chart = self.chart,
  12285. pinchDown = self.pinchDown,
  12286. touches = e.touches,
  12287. touchesLength = touches.length,
  12288. lastValidTouch = self.lastValidTouch,
  12289. hasZoom = self.hasZoom,
  12290. selectionMarker = self.selectionMarker,
  12291. transform = {},
  12292. fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, 'highcharts-tracker') &&
  12293. chart.runTrackerClick) || self.runChartClick),
  12294. clip = {};
  12295. // Don't initiate panning until the user has pinched. This prevents us from
  12296. // blocking page scrolling as users scroll down a long page (#4210).
  12297. if (touchesLength > 1) {
  12298. self.initiated = true;
  12299. }
  12300. // On touch devices, only proceed to trigger click if a handler is defined
  12301. if (hasZoom && self.initiated && !fireClickEvent) {
  12302. e.preventDefault();
  12303. }
  12304. // Normalize each touch
  12305. map(touches, function(e) {
  12306. return self.normalize(e);
  12307. });
  12308. // Register the touch start position
  12309. if (e.type === 'touchstart') {
  12310. each(touches, function(e, i) {
  12311. pinchDown[i] = {
  12312. chartX: e.chartX,
  12313. chartY: e.chartY
  12314. };
  12315. });
  12316. lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX];
  12317. lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY];
  12318. // Identify the data bounds in pixels
  12319. each(chart.axes, function(axis) {
  12320. if (axis.zoomEnabled) {
  12321. var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
  12322. minPixelPadding = axis.minPixelPadding,
  12323. min = axis.toPixels(pick(axis.options.min, axis.dataMin)),
  12324. max = axis.toPixels(pick(axis.options.max, axis.dataMax)),
  12325. absMin = Math.min(min, max),
  12326. absMax = Math.max(min, max);
  12327. // Store the bounds for use in the touchmove handler
  12328. bounds.min = Math.min(axis.pos, absMin - minPixelPadding);
  12329. bounds.max = Math.max(axis.pos + axis.len, absMax + minPixelPadding);
  12330. }
  12331. });
  12332. self.res = true; // reset on next move
  12333. // Optionally move the tooltip on touchmove
  12334. } else if (self.followTouchMove && touchesLength === 1) {
  12335. this.runPointActions(self.normalize(e));
  12336. // Event type is touchmove, handle panning and pinching
  12337. } else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first
  12338. // Set the marker
  12339. if (!selectionMarker) {
  12340. self.selectionMarker = selectionMarker = extend({
  12341. destroy: noop,
  12342. touch: true
  12343. }, chart.plotBox);
  12344. }
  12345. self.pinchTranslate(pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
  12346. self.hasPinched = hasZoom;
  12347. // Scale and translate the groups to provide visual feedback during pinching
  12348. self.scaleGroups(transform, clip);
  12349. if (self.res) {
  12350. self.res = false;
  12351. this.reset(false, 0);
  12352. }
  12353. }
  12354. },
  12355. /**
  12356. * General touch handler shared by touchstart and touchmove.
  12357. */
  12358. touch: function(e, start) {
  12359. var chart = this.chart,
  12360. hasMoved,
  12361. pinchDown,
  12362. isInside;
  12363. if (chart.index !== H.hoverChartIndex) {
  12364. this.onContainerMouseLeave({
  12365. relatedTarget: true
  12366. });
  12367. }
  12368. H.hoverChartIndex = chart.index;
  12369. if (e.touches.length === 1) {
  12370. e = this.normalize(e);
  12371. isInside = chart.isInsidePlot(
  12372. e.chartX - chart.plotLeft,
  12373. e.chartY - chart.plotTop
  12374. );
  12375. if (isInside && !chart.openMenu) {
  12376. // Run mouse events and display tooltip etc
  12377. if (start) {
  12378. this.runPointActions(e);
  12379. }
  12380. // Android fires touchmove events after the touchstart even if the
  12381. // finger hasn't moved, or moved only a pixel or two. In iOS however,
  12382. // the touchmove doesn't fire unless the finger moves more than ~4px.
  12383. // So we emulate this behaviour in Android by checking how much it
  12384. // moved, and cancelling on small distances. #3450.
  12385. if (e.type === 'touchmove') {
  12386. pinchDown = this.pinchDown;
  12387. hasMoved = pinchDown[0] ? Math.sqrt( // #5266
  12388. Math.pow(pinchDown[0].chartX - e.chartX, 2) +
  12389. Math.pow(pinchDown[0].chartY - e.chartY, 2)
  12390. ) >= 4 : false;
  12391. }
  12392. if (pick(hasMoved, true)) {
  12393. this.pinch(e);
  12394. }
  12395. } else if (start) {
  12396. // Hide the tooltip on touching outside the plot area (#1203)
  12397. this.reset();
  12398. }
  12399. } else if (e.touches.length === 2) {
  12400. this.pinch(e);
  12401. }
  12402. },
  12403. onContainerTouchStart: function(e) {
  12404. this.zoomOption(e);
  12405. this.touch(e, true);
  12406. },
  12407. onContainerTouchMove: function(e) {
  12408. this.touch(e);
  12409. },
  12410. onDocumentTouchEnd: function(e) {
  12411. if (charts[H.hoverChartIndex]) {
  12412. charts[H.hoverChartIndex].pointer.drop(e);
  12413. }
  12414. }
  12415. });
  12416. }(Highcharts));
  12417. (function(H) {
  12418. /**
  12419. * (c) 2010-2017 Torstein Honsi
  12420. *
  12421. * License: www.highcharts.com/license
  12422. */
  12423. var addEvent = H.addEvent,
  12424. charts = H.charts,
  12425. css = H.css,
  12426. doc = H.doc,
  12427. extend = H.extend,
  12428. hasTouch = H.hasTouch,
  12429. noop = H.noop,
  12430. Pointer = H.Pointer,
  12431. removeEvent = H.removeEvent,
  12432. win = H.win,
  12433. wrap = H.wrap;
  12434. if (!hasTouch && (win.PointerEvent || win.MSPointerEvent)) {
  12435. // The touches object keeps track of the points being touched at all times
  12436. var touches = {},
  12437. hasPointerEvent = !!win.PointerEvent,
  12438. getWebkitTouches = function() {
  12439. var fake = [];
  12440. fake.item = function(i) {
  12441. return this[i];
  12442. };
  12443. H.objectEach(touches, function(touch) {
  12444. fake.push({
  12445. pageX: touch.pageX,
  12446. pageY: touch.pageY,
  12447. target: touch.target
  12448. });
  12449. });
  12450. return fake;
  12451. },
  12452. translateMSPointer = function(e, method, wktype, func) {
  12453. var p;
  12454. if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[H.hoverChartIndex]) {
  12455. func(e);
  12456. p = charts[H.hoverChartIndex].pointer;
  12457. p[method]({
  12458. type: wktype,
  12459. target: e.currentTarget,
  12460. preventDefault: noop,
  12461. touches: getWebkitTouches()
  12462. });
  12463. }
  12464. };
  12465. /**
  12466. * Extend the Pointer prototype with methods for each event handler and more
  12467. */
  12468. extend(Pointer.prototype, /** @lends Pointer.prototype */ {
  12469. onContainerPointerDown: function(e) {
  12470. translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function(e) {
  12471. touches[e.pointerId] = {
  12472. pageX: e.pageX,
  12473. pageY: e.pageY,
  12474. target: e.currentTarget
  12475. };
  12476. });
  12477. },
  12478. onContainerPointerMove: function(e) {
  12479. translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function(e) {
  12480. touches[e.pointerId] = {
  12481. pageX: e.pageX,
  12482. pageY: e.pageY
  12483. };
  12484. if (!touches[e.pointerId].target) {
  12485. touches[e.pointerId].target = e.currentTarget;
  12486. }
  12487. });
  12488. },
  12489. onDocumentPointerUp: function(e) {
  12490. translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function(e) {
  12491. delete touches[e.pointerId];
  12492. });
  12493. },
  12494. /**
  12495. * Add or remove the MS Pointer specific events
  12496. */
  12497. batchMSEvents: function(fn) {
  12498. fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown);
  12499. fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove);
  12500. fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp);
  12501. }
  12502. });
  12503. // Disable default IE actions for pinch and such on chart element
  12504. wrap(Pointer.prototype, 'init', function(proceed, chart, options) {
  12505. proceed.call(this, chart, options);
  12506. if (this.hasZoom) { // #4014
  12507. css(chart.container, {
  12508. '-ms-touch-action': 'none',
  12509. 'touch-action': 'none'
  12510. });
  12511. }
  12512. });
  12513. // Add IE specific touch events to chart
  12514. wrap(Pointer.prototype, 'setDOMEvents', function(proceed) {
  12515. proceed.apply(this);
  12516. if (this.hasZoom || this.followTouchMove) {
  12517. this.batchMSEvents(addEvent);
  12518. }
  12519. });
  12520. // Destroy MS events also
  12521. wrap(Pointer.prototype, 'destroy', function(proceed) {
  12522. this.batchMSEvents(removeEvent);
  12523. proceed.call(this);
  12524. });
  12525. }
  12526. }(Highcharts));
  12527. (function(Highcharts) {
  12528. /**
  12529. * (c) 2010-2017 Torstein Honsi
  12530. *
  12531. * License: www.highcharts.com/license
  12532. */
  12533. var H = Highcharts,
  12534. addEvent = H.addEvent,
  12535. css = H.css,
  12536. discardElement = H.discardElement,
  12537. defined = H.defined,
  12538. each = H.each,
  12539. isFirefox = H.isFirefox,
  12540. marginNames = H.marginNames,
  12541. merge = H.merge,
  12542. pick = H.pick,
  12543. setAnimation = H.setAnimation,
  12544. stableSort = H.stableSort,
  12545. win = H.win,
  12546. wrap = H.wrap;
  12547. /**
  12548. * The overview of the chart's series. The legend object is instanciated
  12549. * internally in the chart constructor, and available from `chart.legend`. Each
  12550. * chart has only one legend.
  12551. *
  12552. * @class
  12553. */
  12554. Highcharts.Legend = function(chart, options) {
  12555. this.init(chart, options);
  12556. };
  12557. Highcharts.Legend.prototype = {
  12558. /**
  12559. * Initialize the legend
  12560. */
  12561. init: function(chart, options) {
  12562. this.chart = chart;
  12563. this.setOptions(options);
  12564. if (options.enabled) {
  12565. // Render it
  12566. this.render();
  12567. // move checkboxes
  12568. addEvent(this.chart, 'endResize', function() {
  12569. this.legend.positionCheckboxes();
  12570. });
  12571. }
  12572. },
  12573. setOptions: function(options) {
  12574. var padding = pick(options.padding, 8);
  12575. this.options = options;
  12576. this.itemStyle = options.itemStyle;
  12577. this.itemHiddenStyle = merge(this.itemStyle, options.itemHiddenStyle);
  12578. this.itemMarginTop = options.itemMarginTop || 0;
  12579. this.padding = padding;
  12580. this.initialItemY = padding - 5; // 5 is pixels above the text
  12581. this.maxItemWidth = 0;
  12582. this.itemHeight = 0;
  12583. this.symbolWidth = pick(options.symbolWidth, 16);
  12584. this.pages = [];
  12585. },
  12586. /**
  12587. * Update the legend with new options. Equivalent to running `chart.update`
  12588. * with a legend configuration option.
  12589. * @param {LegendOptions} options
  12590. * Legend options.
  12591. * @param {Boolean} [redraw=true]
  12592. * Whether to redraw the chart.
  12593. *
  12594. * @sample highcharts/legend/legend-update/
  12595. * Legend update
  12596. */
  12597. update: function(options, redraw) {
  12598. var chart = this.chart;
  12599. this.setOptions(merge(true, this.options, options));
  12600. this.destroy();
  12601. chart.isDirtyLegend = chart.isDirtyBox = true;
  12602. if (pick(redraw, true)) {
  12603. chart.redraw();
  12604. }
  12605. },
  12606. /**
  12607. * Set the colors for the legend item
  12608. * @param {Object} item A Series or Point instance
  12609. * @param {Object} visible Dimmed or colored
  12610. */
  12611. colorizeItem: function(item, visible) {
  12612. item.legendGroup[visible ? 'removeClass' : 'addClass'](
  12613. 'highcharts-legend-item-hidden'
  12614. );
  12615. var legend = this,
  12616. options = legend.options,
  12617. legendItem = item.legendItem,
  12618. legendLine = item.legendLine,
  12619. legendSymbol = item.legendSymbol,
  12620. hiddenColor = legend.itemHiddenStyle.color,
  12621. textColor = visible ? options.itemStyle.color : hiddenColor,
  12622. symbolColor = visible ? (item.color || hiddenColor) : hiddenColor,
  12623. markerOptions = item.options && item.options.marker,
  12624. symbolAttr = {
  12625. fill: symbolColor
  12626. };
  12627. if (legendItem) {
  12628. legendItem.css({
  12629. fill: textColor,
  12630. color: textColor // #1553, oldIE
  12631. });
  12632. }
  12633. if (legendLine) {
  12634. legendLine.attr({
  12635. stroke: symbolColor
  12636. });
  12637. }
  12638. if (legendSymbol) {
  12639. // Apply marker options
  12640. if (markerOptions && legendSymbol.isMarker) { // #585
  12641. //symbolAttr.stroke = symbolColor;
  12642. symbolAttr = item.pointAttribs();
  12643. if (!visible) {
  12644. H.objectEach(symbolAttr, function(val, key) {
  12645. symbolAttr[key] = hiddenColor;
  12646. });
  12647. }
  12648. }
  12649. legendSymbol.attr(symbolAttr);
  12650. }
  12651. },
  12652. /**
  12653. * Position the legend item
  12654. * @param {Object} item A Series or Point instance
  12655. */
  12656. positionItem: function(item) {
  12657. var legend = this,
  12658. options = legend.options,
  12659. symbolPadding = options.symbolPadding,
  12660. ltr = !options.rtl,
  12661. legendItemPos = item._legendItemPos,
  12662. itemX = legendItemPos[0],
  12663. itemY = legendItemPos[1],
  12664. checkbox = item.checkbox,
  12665. legendGroup = item.legendGroup;
  12666. if (legendGroup && legendGroup.element) {
  12667. legendGroup.translate(
  12668. ltr ?
  12669. itemX :
  12670. legend.legendWidth - itemX - 2 * symbolPadding - 4,
  12671. itemY
  12672. );
  12673. }
  12674. if (checkbox) {
  12675. checkbox.x = itemX;
  12676. checkbox.y = itemY;
  12677. }
  12678. },
  12679. /**
  12680. * Destroy a single legend item
  12681. * @param {Object} item The series or point
  12682. */
  12683. destroyItem: function(item) {
  12684. var checkbox = item.checkbox;
  12685. // destroy SVG elements
  12686. each(
  12687. ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'],
  12688. function(key) {
  12689. if (item[key]) {
  12690. item[key] = item[key].destroy();
  12691. }
  12692. }
  12693. );
  12694. if (checkbox) {
  12695. discardElement(item.checkbox);
  12696. }
  12697. },
  12698. /**
  12699. * Destroys the legend.
  12700. */
  12701. destroy: function() {
  12702. function destroyItems(key) {
  12703. if (this[key]) {
  12704. this[key] = this[key].destroy();
  12705. }
  12706. }
  12707. // Destroy items
  12708. each(this.getAllItems(), function(item) {
  12709. each(['legendItem', 'legendGroup'], destroyItems, item);
  12710. });
  12711. // Destroy legend elements
  12712. each([
  12713. 'clipRect',
  12714. 'up',
  12715. 'down',
  12716. 'pager',
  12717. 'nav',
  12718. 'box',
  12719. 'title',
  12720. 'group'
  12721. ], destroyItems, this);
  12722. this.display = null; // Reset in .render on update.
  12723. },
  12724. /**
  12725. * Position the checkboxes after the width is determined
  12726. */
  12727. positionCheckboxes: function(scrollOffset) {
  12728. var alignAttr = this.group && this.group.alignAttr,
  12729. translateY,
  12730. clipHeight = this.clipHeight || this.legendHeight,
  12731. titleHeight = this.titleHeight;
  12732. if (alignAttr) {
  12733. translateY = alignAttr.translateY;
  12734. each(this.allItems, function(item) {
  12735. var checkbox = item.checkbox,
  12736. top;
  12737. if (checkbox) {
  12738. top = translateY + titleHeight + checkbox.y +
  12739. (scrollOffset || 0) + 3;
  12740. css(checkbox, {
  12741. left: (alignAttr.translateX + item.checkboxOffset +
  12742. checkbox.x - 20) + 'px',
  12743. top: top + 'px',
  12744. display: top > translateY - 6 && top < translateY +
  12745. clipHeight - 6 ? '' : 'none'
  12746. });
  12747. }
  12748. });
  12749. }
  12750. },
  12751. /**
  12752. * Render the legend title on top of the legend
  12753. */
  12754. renderTitle: function() {
  12755. var options = this.options,
  12756. padding = this.padding,
  12757. titleOptions = options.title,
  12758. titleHeight = 0,
  12759. bBox;
  12760. if (titleOptions.text) {
  12761. if (!this.title) {
  12762. this.title = this.chart.renderer.label(
  12763. titleOptions.text,
  12764. padding - 3,
  12765. padding - 4,
  12766. null,
  12767. null,
  12768. null,
  12769. options.useHTML,
  12770. null,
  12771. 'legend-title'
  12772. )
  12773. .attr({
  12774. zIndex: 1
  12775. })
  12776. .css(titleOptions.style)
  12777. .add(this.group);
  12778. }
  12779. bBox = this.title.getBBox();
  12780. titleHeight = bBox.height;
  12781. this.offsetWidth = bBox.width; // #1717
  12782. this.contentGroup.attr({
  12783. translateY: titleHeight
  12784. });
  12785. }
  12786. this.titleHeight = titleHeight;
  12787. },
  12788. /**
  12789. * Set the legend item text
  12790. */
  12791. setText: function(item) {
  12792. var options = this.options;
  12793. item.legendItem.attr({
  12794. text: options.labelFormat ?
  12795. H.format(options.labelFormat, item) : options.labelFormatter.call(item)
  12796. });
  12797. },
  12798. /**
  12799. * Render a single specific legend item
  12800. * @param {Object} item A series or point
  12801. */
  12802. renderItem: function(item) {
  12803. var legend = this,
  12804. chart = legend.chart,
  12805. renderer = chart.renderer,
  12806. options = legend.options,
  12807. horizontal = options.layout === 'horizontal',
  12808. symbolWidth = legend.symbolWidth,
  12809. symbolPadding = options.symbolPadding,
  12810. itemStyle = legend.itemStyle,
  12811. itemHiddenStyle = legend.itemHiddenStyle,
  12812. padding = legend.padding,
  12813. itemDistance = horizontal ? pick(options.itemDistance, 20) : 0,
  12814. ltr = !options.rtl,
  12815. itemHeight,
  12816. widthOption = options.width,
  12817. itemMarginBottom = options.itemMarginBottom || 0,
  12818. itemMarginTop = legend.itemMarginTop,
  12819. bBox,
  12820. itemWidth,
  12821. li = item.legendItem,
  12822. isSeries = !item.series,
  12823. series = !isSeries && item.series.drawLegendSymbol ?
  12824. item.series :
  12825. item,
  12826. seriesOptions = series.options,
  12827. showCheckbox = legend.createCheckboxForItem &&
  12828. seriesOptions &&
  12829. seriesOptions.showCheckbox,
  12830. // full width minus text width
  12831. itemExtraWidth = symbolWidth + symbolPadding + itemDistance +
  12832. (showCheckbox ? 20 : 0),
  12833. useHTML = options.useHTML,
  12834. fontSize = 12,
  12835. itemClassName = item.options.className;
  12836. if (!li) { // generate it once, later move it
  12837. // Generate the group box, a group to hold the symbol and text. Text
  12838. // is to be appended in Legend class.
  12839. item.legendGroup = renderer.g('legend-item')
  12840. .addClass(
  12841. 'highcharts-' + series.type + '-series ' +
  12842. 'highcharts-color-' + item.colorIndex +
  12843. (itemClassName ? ' ' + itemClassName : '') +
  12844. (isSeries ? ' highcharts-series-' + item.index : '')
  12845. )
  12846. .attr({
  12847. zIndex: 1
  12848. })
  12849. .add(legend.scrollGroup);
  12850. // Generate the list item text and add it to the group
  12851. item.legendItem = li = renderer.text(
  12852. '',
  12853. ltr ? symbolWidth + symbolPadding : -symbolPadding,
  12854. legend.baseline || 0,
  12855. useHTML
  12856. )
  12857. // merge to prevent modifying original (#1021)
  12858. .css(merge(item.visible ? itemStyle : itemHiddenStyle))
  12859. .attr({
  12860. align: ltr ? 'left' : 'right',
  12861. zIndex: 2
  12862. })
  12863. .add(item.legendGroup);
  12864. // Get the baseline for the first item - the font size is equal for
  12865. // all
  12866. if (!legend.baseline) {
  12867. fontSize = itemStyle.fontSize;
  12868. legend.fontMetrics = renderer.fontMetrics(
  12869. fontSize,
  12870. li
  12871. );
  12872. legend.baseline = legend.fontMetrics.f + 3 + itemMarginTop;
  12873. li.attr('y', legend.baseline);
  12874. }
  12875. // Draw the legend symbol inside the group box
  12876. legend.symbolHeight = options.symbolHeight || legend.fontMetrics.f;
  12877. series.drawLegendSymbol(legend, item);
  12878. if (legend.setItemEvents) {
  12879. legend.setItemEvents(item, li, useHTML);
  12880. }
  12881. // add the HTML checkbox on top
  12882. if (showCheckbox) {
  12883. legend.createCheckboxForItem(item);
  12884. }
  12885. }
  12886. // Colorize the items
  12887. legend.colorizeItem(item, item.visible);
  12888. // Take care of max width and text overflow (#6659)
  12889. if (!itemStyle.width) {
  12890. li.css({
  12891. width: (options.itemWidth || chart.spacingBox.width) -
  12892. itemExtraWidth
  12893. });
  12894. }
  12895. // Always update the text
  12896. legend.setText(item);
  12897. // calculate the positions for the next line
  12898. bBox = li.getBBox();
  12899. itemWidth = item.checkboxOffset =
  12900. options.itemWidth ||
  12901. item.legendItemWidth ||
  12902. bBox.width + itemExtraWidth;
  12903. legend.itemHeight = itemHeight = Math.round(
  12904. item.legendItemHeight || bBox.height || legend.symbolHeight
  12905. );
  12906. // If the item exceeds the width, start a new line
  12907. if (
  12908. horizontal &&
  12909. legend.itemX - padding + itemWidth > (
  12910. widthOption || (
  12911. chart.spacingBox.width - 2 * padding - options.x
  12912. )
  12913. )
  12914. ) {
  12915. legend.itemX = padding;
  12916. legend.itemY += itemMarginTop + legend.lastLineHeight +
  12917. itemMarginBottom;
  12918. legend.lastLineHeight = 0; // reset for next line (#915, #3976)
  12919. }
  12920. // If the item exceeds the height, start a new column
  12921. /*if (!horizontal && legend.itemY + options.y +
  12922. itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
  12923. legend.itemY = legend.initialItemY;
  12924. legend.itemX += legend.maxItemWidth;
  12925. legend.maxItemWidth = 0;
  12926. }*/
  12927. // Set the edge positions
  12928. legend.maxItemWidth = Math.max(legend.maxItemWidth, itemWidth);
  12929. legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
  12930. legend.lastLineHeight = Math.max( // #915
  12931. itemHeight,
  12932. legend.lastLineHeight
  12933. );
  12934. // cache the position of the newly generated or reordered items
  12935. item._legendItemPos = [legend.itemX, legend.itemY];
  12936. // advance
  12937. if (horizontal) {
  12938. legend.itemX += itemWidth;
  12939. } else {
  12940. legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
  12941. legend.lastLineHeight = itemHeight;
  12942. }
  12943. // the width of the widest item
  12944. legend.offsetWidth = widthOption || Math.max(
  12945. (horizontal ? legend.itemX - padding - itemDistance : itemWidth) +
  12946. padding,
  12947. legend.offsetWidth
  12948. );
  12949. },
  12950. /**
  12951. * Get all items, which is one item per series for normal series and one
  12952. * item per point for pie series.
  12953. */
  12954. getAllItems: function() {
  12955. var allItems = [];
  12956. each(this.chart.series, function(series) {
  12957. var seriesOptions = series && series.options;
  12958. // Handle showInLegend. If the series is linked to another series,
  12959. // defaults to false.
  12960. if (series && pick(
  12961. seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? undefined : false, true
  12962. )) {
  12963. // Use points or series for the legend item depending on
  12964. // legendType
  12965. allItems = allItems.concat(
  12966. series.legendItems ||
  12967. (
  12968. seriesOptions.legendType === 'point' ?
  12969. series.data :
  12970. series
  12971. )
  12972. );
  12973. }
  12974. });
  12975. return allItems;
  12976. },
  12977. /**
  12978. * Adjust the chart margins by reserving space for the legend on only one
  12979. * side of the chart. If the position is set to a corner, top or bottom is
  12980. * reserved for horizontal legends and left or right for vertical ones.
  12981. */
  12982. adjustMargins: function(margin, spacing) {
  12983. var chart = this.chart,
  12984. options = this.options,
  12985. // Use the first letter of each alignment option in order to detect
  12986. // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
  12987. alignment = options.align.charAt(0) +
  12988. options.verticalAlign.charAt(0) +
  12989. options.layout.charAt(0);
  12990. if (!options.floating) {
  12991. each([
  12992. /(lth|ct|rth)/,
  12993. /(rtv|rm|rbv)/,
  12994. /(rbh|cb|lbh)/,
  12995. /(lbv|lm|ltv)/
  12996. ], function(alignments, side) {
  12997. if (alignments.test(alignment) && !defined(margin[side])) {
  12998. // Now we have detected on which side of the chart we should
  12999. // reserve space for the legend
  13000. chart[marginNames[side]] = Math.max(
  13001. chart[marginNames[side]],
  13002. (
  13003. chart.legend[
  13004. (side + 1) % 2 ? 'legendHeight' : 'legendWidth'
  13005. ] + [1, -1, -1, 1][side] * options[
  13006. (side % 2) ? 'x' : 'y'
  13007. ] +
  13008. pick(options.margin, 12) +
  13009. spacing[side]
  13010. )
  13011. );
  13012. }
  13013. });
  13014. }
  13015. },
  13016. /**
  13017. * Render the legend. This method can be called both before and after
  13018. * chart.render. If called after, it will only rearrange items instead
  13019. * of creating new ones.
  13020. */
  13021. render: function() {
  13022. var legend = this,
  13023. chart = legend.chart,
  13024. renderer = chart.renderer,
  13025. legendGroup = legend.group,
  13026. allItems,
  13027. display,
  13028. legendWidth,
  13029. legendHeight,
  13030. box = legend.box,
  13031. options = legend.options,
  13032. padding = legend.padding;
  13033. legend.itemX = padding;
  13034. legend.itemY = legend.initialItemY;
  13035. legend.offsetWidth = 0;
  13036. legend.lastItemY = 0;
  13037. if (!legendGroup) {
  13038. legend.group = legendGroup = renderer.g('legend')
  13039. .attr({
  13040. zIndex: 7
  13041. })
  13042. .add();
  13043. legend.contentGroup = renderer.g()
  13044. .attr({
  13045. zIndex: 1
  13046. }) // above background
  13047. .add(legendGroup);
  13048. legend.scrollGroup = renderer.g()
  13049. .add(legend.contentGroup);
  13050. }
  13051. legend.renderTitle();
  13052. // add each series or point
  13053. allItems = legend.getAllItems();
  13054. // sort by legendIndex
  13055. stableSort(allItems, function(a, b) {
  13056. return ((a.options && a.options.legendIndex) || 0) -
  13057. ((b.options && b.options.legendIndex) || 0);
  13058. });
  13059. // reversed legend
  13060. if (options.reversed) {
  13061. allItems.reverse();
  13062. }
  13063. legend.allItems = allItems;
  13064. legend.display = display = !!allItems.length;
  13065. // render the items
  13066. legend.lastLineHeight = 0;
  13067. each(allItems, function(item) {
  13068. legend.renderItem(item);
  13069. });
  13070. // Get the box
  13071. legendWidth = (options.width || legend.offsetWidth) + padding;
  13072. legendHeight = legend.lastItemY + legend.lastLineHeight +
  13073. legend.titleHeight;
  13074. legendHeight = legend.handleOverflow(legendHeight);
  13075. legendHeight += padding;
  13076. // Draw the border and/or background
  13077. if (!box) {
  13078. legend.box = box = renderer.rect()
  13079. .addClass('highcharts-legend-box')
  13080. .attr({
  13081. r: options.borderRadius
  13082. })
  13083. .add(legendGroup);
  13084. box.isNew = true;
  13085. }
  13086. // Presentational
  13087. box
  13088. .attr({
  13089. stroke: options.borderColor,
  13090. 'stroke-width': options.borderWidth || 0,
  13091. fill: options.backgroundColor || 'none'
  13092. })
  13093. .shadow(options.shadow);
  13094. if (legendWidth > 0 && legendHeight > 0) {
  13095. box[box.isNew ? 'attr' : 'animate'](
  13096. box.crisp({
  13097. x: 0,
  13098. y: 0,
  13099. width: legendWidth,
  13100. height: legendHeight
  13101. }, box.strokeWidth())
  13102. );
  13103. box.isNew = false;
  13104. }
  13105. // hide the border if no items
  13106. box[display ? 'show' : 'hide']();
  13107. legend.legendWidth = legendWidth;
  13108. legend.legendHeight = legendHeight;
  13109. // Now that the legend width and height are established, put the items
  13110. // in the final position
  13111. each(allItems, function(item) {
  13112. legend.positionItem(item);
  13113. });
  13114. // 1.x compatibility: positioning based on style
  13115. /*var props = ['left', 'right', 'top', 'bottom'],
  13116. prop,
  13117. i = 4;
  13118. while (i--) {
  13119. prop = props[i];
  13120. if (options.style[prop] && options.style[prop] !== 'auto') {
  13121. options[i < 2 ? 'align' : 'verticalAlign'] = prop;
  13122. options[i < 2 ? 'x' : 'y'] =
  13123. pInt(options.style[prop]) * (i % 2 ? -1 : 1);
  13124. }
  13125. }*/
  13126. if (display) {
  13127. legendGroup.align(merge(options, {
  13128. width: legendWidth,
  13129. height: legendHeight
  13130. }), true, 'spacingBox');
  13131. }
  13132. if (!chart.isResizing) {
  13133. this.positionCheckboxes();
  13134. }
  13135. },
  13136. /**
  13137. * Set up the overflow handling by adding navigation with up and down arrows
  13138. * below the legend.
  13139. */
  13140. handleOverflow: function(legendHeight) {
  13141. var legend = this,
  13142. chart = this.chart,
  13143. renderer = chart.renderer,
  13144. options = this.options,
  13145. optionsY = options.y,
  13146. alignTop = options.verticalAlign === 'top',
  13147. padding = this.padding,
  13148. spaceHeight = chart.spacingBox.height +
  13149. (alignTop ? -optionsY : optionsY) - padding,
  13150. maxHeight = options.maxHeight,
  13151. clipHeight,
  13152. clipRect = this.clipRect,
  13153. navOptions = options.navigation,
  13154. animation = pick(navOptions.animation, true),
  13155. arrowSize = navOptions.arrowSize || 12,
  13156. nav = this.nav,
  13157. pages = this.pages,
  13158. lastY,
  13159. allItems = this.allItems,
  13160. clipToHeight = function(height) {
  13161. if (typeof height === 'number') {
  13162. clipRect.attr({
  13163. height: height
  13164. });
  13165. } else if (clipRect) { // Reset (#5912)
  13166. legend.clipRect = clipRect.destroy();
  13167. legend.contentGroup.clip();
  13168. }
  13169. // useHTML
  13170. if (legend.contentGroup.div) {
  13171. legend.contentGroup.div.style.clip = height ?
  13172. 'rect(' + padding + 'px,9999px,' +
  13173. (padding + height) + 'px,0)' :
  13174. 'auto';
  13175. }
  13176. };
  13177. // Adjust the height
  13178. if (
  13179. options.layout === 'horizontal' &&
  13180. options.verticalAlign !== 'middle' &&
  13181. !options.floating
  13182. ) {
  13183. spaceHeight /= 2;
  13184. }
  13185. if (maxHeight) {
  13186. spaceHeight = Math.min(spaceHeight, maxHeight);
  13187. }
  13188. // Reset the legend height and adjust the clipping rectangle
  13189. pages.length = 0;
  13190. if (legendHeight > spaceHeight && navOptions.enabled !== false) {
  13191. this.clipHeight = clipHeight =
  13192. Math.max(spaceHeight - 20 - this.titleHeight - padding, 0);
  13193. this.currentPage = pick(this.currentPage, 1);
  13194. this.fullHeight = legendHeight;
  13195. // Fill pages with Y positions so that the top of each a legend item
  13196. // defines the scroll top for each page (#2098)
  13197. each(allItems, function(item, i) {
  13198. var y = item._legendItemPos[1],
  13199. h = Math.round(item.legendItem.getBBox().height),
  13200. len = pages.length;
  13201. if (!len || (y - pages[len - 1] > clipHeight &&
  13202. (lastY || y) !== pages[len - 1])) {
  13203. pages.push(lastY || y);
  13204. len++;
  13205. }
  13206. if (i === allItems.length - 1 &&
  13207. y + h - pages[len - 1] > clipHeight) {
  13208. pages.push(y);
  13209. }
  13210. if (y !== lastY) {
  13211. lastY = y;
  13212. }
  13213. });
  13214. // Only apply clipping if needed. Clipping causes blurred legend in
  13215. // PDF export (#1787)
  13216. if (!clipRect) {
  13217. clipRect = legend.clipRect =
  13218. renderer.clipRect(0, padding, 9999, 0);
  13219. legend.contentGroup.clip(clipRect);
  13220. }
  13221. clipToHeight(clipHeight);
  13222. // Add navigation elements
  13223. if (!nav) {
  13224. this.nav = nav = renderer.g()
  13225. .attr({
  13226. zIndex: 1
  13227. })
  13228. .add(this.group);
  13229. this.up = renderer
  13230. .symbol(
  13231. 'triangle',
  13232. 0,
  13233. 0,
  13234. arrowSize,
  13235. arrowSize
  13236. )
  13237. .on('click', function() {
  13238. legend.scroll(-1, animation);
  13239. })
  13240. .add(nav);
  13241. this.pager = renderer.text('', 15, 10)
  13242. .addClass('highcharts-legend-navigation')
  13243. .css(navOptions.style)
  13244. .add(nav);
  13245. this.down = renderer
  13246. .symbol(
  13247. 'triangle-down',
  13248. 0,
  13249. 0,
  13250. arrowSize,
  13251. arrowSize
  13252. )
  13253. .on('click', function() {
  13254. legend.scroll(1, animation);
  13255. })
  13256. .add(nav);
  13257. }
  13258. // Set initial position
  13259. legend.scroll(0);
  13260. legendHeight = spaceHeight;
  13261. // Reset
  13262. } else if (nav) {
  13263. clipToHeight();
  13264. this.nav = nav.destroy(); // #6322
  13265. this.scrollGroup.attr({
  13266. translateY: 1
  13267. });
  13268. this.clipHeight = 0; // #1379
  13269. }
  13270. return legendHeight;
  13271. },
  13272. /**
  13273. * Scroll the legend by a number of pages
  13274. * @param {Object} scrollBy
  13275. * @param {Object} animation
  13276. */
  13277. scroll: function(scrollBy, animation) {
  13278. var pages = this.pages,
  13279. pageCount = pages.length,
  13280. currentPage = this.currentPage + scrollBy,
  13281. clipHeight = this.clipHeight,
  13282. navOptions = this.options.navigation,
  13283. pager = this.pager,
  13284. padding = this.padding,
  13285. scrollOffset;
  13286. // When resizing while looking at the last page
  13287. if (currentPage > pageCount) {
  13288. currentPage = pageCount;
  13289. }
  13290. if (currentPage > 0) {
  13291. if (animation !== undefined) {
  13292. setAnimation(animation, this.chart);
  13293. }
  13294. this.nav.attr({
  13295. translateX: padding,
  13296. translateY: clipHeight + this.padding + 7 + this.titleHeight,
  13297. visibility: 'visible'
  13298. });
  13299. this.up.attr({
  13300. 'class': currentPage === 1 ?
  13301. 'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active'
  13302. });
  13303. pager.attr({
  13304. text: currentPage + '/' + pageCount
  13305. });
  13306. this.down.attr({
  13307. 'x': 18 + this.pager.getBBox().width, // adjust to text width
  13308. 'class': currentPage === pageCount ?
  13309. 'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active'
  13310. });
  13311. this.up
  13312. .attr({
  13313. fill: currentPage === 1 ?
  13314. navOptions.inactiveColor : navOptions.activeColor
  13315. })
  13316. .css({
  13317. cursor: currentPage === 1 ? 'default' : 'pointer'
  13318. });
  13319. this.down
  13320. .attr({
  13321. fill: currentPage === pageCount ?
  13322. navOptions.inactiveColor : navOptions.activeColor
  13323. })
  13324. .css({
  13325. cursor: currentPage === pageCount ? 'default' : 'pointer'
  13326. });
  13327. scrollOffset = -pages[currentPage - 1] + this.initialItemY;
  13328. this.scrollGroup.animate({
  13329. translateY: scrollOffset
  13330. });
  13331. this.currentPage = currentPage;
  13332. this.positionCheckboxes(scrollOffset);
  13333. }
  13334. }
  13335. };
  13336. /*
  13337. * LegendSymbolMixin
  13338. */
  13339. H.LegendSymbolMixin = {
  13340. /**
  13341. * Get the series' symbol in the legend
  13342. *
  13343. * @param {Object} legend The legend object
  13344. * @param {Object} item The series (this) or point
  13345. */
  13346. drawRectangle: function(legend, item) {
  13347. var options = legend.options,
  13348. symbolHeight = legend.symbolHeight,
  13349. square = options.squareSymbol,
  13350. symbolWidth = square ? symbolHeight : legend.symbolWidth;
  13351. item.legendSymbol = this.chart.renderer.rect(
  13352. square ? (legend.symbolWidth - symbolHeight) / 2 : 0,
  13353. legend.baseline - symbolHeight + 1, // #3988
  13354. symbolWidth,
  13355. symbolHeight,
  13356. pick(legend.options.symbolRadius, symbolHeight / 2)
  13357. )
  13358. .addClass('highcharts-point')
  13359. .attr({
  13360. zIndex: 3
  13361. }).add(item.legendGroup);
  13362. },
  13363. /**
  13364. * Get the series' symbol in the legend. This method should be overridable
  13365. * to create custom symbols through
  13366. * Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
  13367. *
  13368. * @param {Object} legend The legend object
  13369. */
  13370. drawLineMarker: function(legend) {
  13371. var options = this.options,
  13372. markerOptions = options.marker,
  13373. radius,
  13374. legendSymbol,
  13375. symbolWidth = legend.symbolWidth,
  13376. symbolHeight = legend.symbolHeight,
  13377. generalRadius = symbolHeight / 2,
  13378. renderer = this.chart.renderer,
  13379. legendItemGroup = this.legendGroup,
  13380. verticalCenter = legend.baseline -
  13381. Math.round(legend.fontMetrics.b * 0.3),
  13382. attr = {};
  13383. // Draw the line
  13384. attr = {
  13385. 'stroke-width': options.lineWidth || 0
  13386. };
  13387. if (options.dashStyle) {
  13388. attr.dashstyle = options.dashStyle;
  13389. }
  13390. this.legendLine = renderer.path([
  13391. 'M',
  13392. 0,
  13393. verticalCenter,
  13394. 'L',
  13395. symbolWidth,
  13396. verticalCenter
  13397. ])
  13398. .addClass('highcharts-graph')
  13399. .attr(attr)
  13400. .add(legendItemGroup);
  13401. // Draw the marker
  13402. if (markerOptions && markerOptions.enabled !== false) {
  13403. // Do not allow the marker to be larger than the symbolHeight
  13404. radius = Math.min(
  13405. pick(markerOptions.radius, generalRadius),
  13406. generalRadius
  13407. );
  13408. // Restrict symbol markers size
  13409. if (this.symbol.indexOf('url') === 0) {
  13410. markerOptions = merge(markerOptions, {
  13411. width: symbolHeight,
  13412. height: symbolHeight
  13413. });
  13414. radius = 0;
  13415. }
  13416. this.legendSymbol = legendSymbol = renderer.symbol(
  13417. this.symbol,
  13418. (symbolWidth / 2) - radius,
  13419. verticalCenter - radius,
  13420. 2 * radius,
  13421. 2 * radius,
  13422. markerOptions
  13423. )
  13424. .addClass('highcharts-point')
  13425. .add(legendItemGroup);
  13426. legendSymbol.isMarker = true;
  13427. }
  13428. }
  13429. };
  13430. // Workaround for #2030, horizontal legend items not displaying in IE11 Preview,
  13431. // and for #2580, a similar drawing flaw in Firefox 26.
  13432. // Explore if there's a general cause for this. The problem may be related
  13433. // to nested group elements, as the legend item texts are within 4 group
  13434. // elements.
  13435. if (/Trident\/7\.0/.test(win.navigator.userAgent) || isFirefox) {
  13436. wrap(Highcharts.Legend.prototype, 'positionItem', function(proceed, item) {
  13437. var legend = this,
  13438. // If chart destroyed in sync, this is undefined (#2030)
  13439. runPositionItem = function() {
  13440. if (item._legendItemPos) {
  13441. proceed.call(legend, item);
  13442. }
  13443. };
  13444. // Do it now, for export and to get checkbox placement
  13445. runPositionItem();
  13446. // Do it after to work around the core issue
  13447. setTimeout(runPositionItem);
  13448. });
  13449. }
  13450. }(Highcharts));
  13451. (function(H) {
  13452. /**
  13453. * (c) 2010-2017 Torstein Honsi
  13454. *
  13455. * License: www.highcharts.com/license
  13456. */
  13457. var addEvent = H.addEvent,
  13458. animate = H.animate,
  13459. animObject = H.animObject,
  13460. attr = H.attr,
  13461. doc = H.doc,
  13462. Axis = H.Axis, // @todo add as requirement
  13463. createElement = H.createElement,
  13464. defaultOptions = H.defaultOptions,
  13465. discardElement = H.discardElement,
  13466. charts = H.charts,
  13467. css = H.css,
  13468. defined = H.defined,
  13469. each = H.each,
  13470. extend = H.extend,
  13471. find = H.find,
  13472. fireEvent = H.fireEvent,
  13473. getStyle = H.getStyle,
  13474. grep = H.grep,
  13475. isNumber = H.isNumber,
  13476. isObject = H.isObject,
  13477. isString = H.isString,
  13478. Legend = H.Legend, // @todo add as requirement
  13479. marginNames = H.marginNames,
  13480. merge = H.merge,
  13481. objectEach = H.objectEach,
  13482. Pointer = H.Pointer, // @todo add as requirement
  13483. pick = H.pick,
  13484. pInt = H.pInt,
  13485. removeEvent = H.removeEvent,
  13486. seriesTypes = H.seriesTypes,
  13487. splat = H.splat,
  13488. svg = H.svg,
  13489. syncTimeout = H.syncTimeout,
  13490. win = H.win,
  13491. Renderer = H.Renderer;
  13492. /**
  13493. * The Chart class. The recommended constructor is {@link Highcharts#chart}.
  13494. * @class Highcharts.Chart
  13495. * @param {String|HTMLDOMElement} renderTo
  13496. * The DOM element to render to, or its id.
  13497. * @param {Options} options
  13498. * The chart options structure.
  13499. * @param {Function} [callback]
  13500. * Function to run when the chart has loaded and and all external images
  13501. * are loaded. Defining a {@link
  13502. * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
  13503. * handler is equivalent.
  13504. *
  13505. * @example
  13506. * var chart = new Highcharts.Chart('container', {
  13507. * title: {
  13508. * text: 'My chart'
  13509. * },
  13510. * series: [{
  13511. * data: [1, 3, 2, 4]
  13512. * }]
  13513. * })
  13514. */
  13515. var Chart = H.Chart = function() {
  13516. this.getArgs.apply(this, arguments);
  13517. };
  13518. /**
  13519. * Factory function for basic charts.
  13520. *
  13521. * @function #chart
  13522. * @memberOf Highcharts
  13523. * @param {String|HTMLDOMElement} renderTo - The DOM element to render to, or
  13524. * its id.
  13525. * @param {Options} options - The chart options structure.
  13526. * @param {Function} [callback] - Function to run when the chart has loaded and
  13527. * and all external images are loaded. Defining a {@link
  13528. * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
  13529. * handler is equivalent.
  13530. * @return {Highcharts.Chart} - Returns the Chart object.
  13531. *
  13532. * @example
  13533. * // Render a chart in to div#container
  13534. * var chart = Highcharts.chart('container', {
  13535. * title: {
  13536. * text: 'My chart'
  13537. * },
  13538. * series: [{
  13539. * data: [1, 3, 2, 4]
  13540. * }]
  13541. * });
  13542. */
  13543. H.chart = function(a, b, c) {
  13544. return new Chart(a, b, c);
  13545. };
  13546. extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
  13547. /**
  13548. * Hook for modules
  13549. */
  13550. callbacks: [],
  13551. /**
  13552. * Handle the arguments passed to the constructor
  13553. * @returns {Array} Arguments without renderTo
  13554. */
  13555. getArgs: function() {
  13556. var args = [].slice.call(arguments);
  13557. // Remove the optional first argument, renderTo, and
  13558. // set it on this.
  13559. if (isString(args[0]) || args[0].nodeName) {
  13560. this.renderTo = args.shift();
  13561. }
  13562. this.init(args[0], args[1]);
  13563. },
  13564. /**
  13565. * Initialize the chart
  13566. */
  13567. init: function(userOptions, callback) {
  13568. // Handle regular options
  13569. var options,
  13570. type,
  13571. seriesOptions = userOptions.series, // skip merging data points to increase performance
  13572. userPlotOptions = userOptions.plotOptions || {};
  13573. userOptions.series = null;
  13574. options = merge(defaultOptions, userOptions); // do the merge
  13575. // Override (by copy of user options) or clear tooltip options
  13576. // in chart.options.plotOptions (#6218)
  13577. for (type in options.plotOptions) {
  13578. options.plotOptions[type].tooltip = (
  13579. userPlotOptions[type] &&
  13580. merge(userPlotOptions[type].tooltip) // override by copy
  13581. ) || undefined; // or clear
  13582. }
  13583. // User options have higher priority than default options (#6218).
  13584. // In case of exporting: path is changed
  13585. options.tooltip.userOptions = (userOptions.chart &&
  13586. userOptions.chart.forExport && userOptions.tooltip.userOptions) ||
  13587. userOptions.tooltip;
  13588. options.series = userOptions.series = seriesOptions; // set back the series data
  13589. this.userOptions = userOptions;
  13590. var optionsChart = options.chart;
  13591. var chartEvents = optionsChart.events;
  13592. this.margin = [];
  13593. this.spacing = [];
  13594. //this.runChartClick = chartEvents && !!chartEvents.click;
  13595. this.bounds = {
  13596. h: {},
  13597. v: {}
  13598. }; // Pixel data bounds for touch zoom
  13599. this.callback = callback;
  13600. this.isResizing = 0;
  13601. /**
  13602. * The options structure for the chart. It contains members for the sub
  13603. * elements like series, legend, tooltip etc.
  13604. *
  13605. * @memberof Highcharts.Chart
  13606. * @name options
  13607. * @type {Options}
  13608. */
  13609. this.options = options;
  13610. /**
  13611. * All the axes in the chart.
  13612. *
  13613. * @memberof Highcharts.Chart
  13614. * @name axes
  13615. * @see Highcharts.Chart.xAxis
  13616. * @see Highcharts.Chart.yAxis
  13617. * @type {Array.<Highcharts.Axis>}
  13618. */
  13619. this.axes = [];
  13620. /**
  13621. * All the current series in the chart.
  13622. *
  13623. * @memberof Highcharts.Chart
  13624. * @name series
  13625. * @type {Array.<Highcharts.Series>}
  13626. */
  13627. this.series = [];
  13628. /**
  13629. * The chart title. The title has an `update` method that allows
  13630. * modifying the options directly or indirectly via `chart.update`.
  13631. *
  13632. * @memberof Highcharts.Chart
  13633. * @name title
  13634. * @type Object
  13635. *
  13636. * @sample highcharts/members/title-update/
  13637. * Updating titles
  13638. */
  13639. /**
  13640. * The chart subtitle. The subtitle has an `update` method that allows
  13641. * modifying the options directly or indirectly via `chart.update`.
  13642. *
  13643. * @memberof Highcharts.Chart
  13644. * @name subtitle
  13645. * @type Object
  13646. */
  13647. this.hasCartesianSeries = optionsChart.showAxes;
  13648. //this.axisOffset = undefined;
  13649. //this.inverted = undefined;
  13650. //this.loadingShown = undefined;
  13651. //this.container = undefined;
  13652. //this.chartWidth = undefined;
  13653. //this.chartHeight = undefined;
  13654. //this.marginRight = undefined;
  13655. //this.marginBottom = undefined;
  13656. //this.containerWidth = undefined;
  13657. //this.containerHeight = undefined;
  13658. //this.oldChartWidth = undefined;
  13659. //this.oldChartHeight = undefined;
  13660. //this.renderTo = undefined;
  13661. //this.spacingBox = undefined
  13662. //this.legend = undefined;
  13663. // Elements
  13664. //this.chartBackground = undefined;
  13665. //this.plotBackground = undefined;
  13666. //this.plotBGImage = undefined;
  13667. //this.plotBorder = undefined;
  13668. //this.loadingDiv = undefined;
  13669. //this.loadingSpan = undefined;
  13670. var chart = this;
  13671. // Add the chart to the global lookup
  13672. chart.index = charts.length;
  13673. charts.push(chart);
  13674. H.chartCount++;
  13675. // Chart event handlers
  13676. if (chartEvents) {
  13677. objectEach(chartEvents, function(event, eventType) {
  13678. addEvent(chart, eventType, event);
  13679. });
  13680. }
  13681. /**
  13682. * A collection of the X axes in the chart.
  13683. * @type {Array.<Highcharts.Axis>}
  13684. * @name xAxis
  13685. * @memberOf Highcharts.Chart
  13686. */
  13687. chart.xAxis = [];
  13688. /**
  13689. * A collection of the Y axes in the chart.
  13690. * @type {Array.<Highcharts.Axis>}
  13691. * @name yAxis
  13692. * @memberOf Highcharts.Chart
  13693. */
  13694. chart.yAxis = [];
  13695. chart.pointCount = chart.colorCounter = chart.symbolCounter = 0;
  13696. chart.firstRender();
  13697. },
  13698. /**
  13699. * Initialize an individual series, called internally before render time
  13700. */
  13701. initSeries: function(options) {
  13702. var chart = this,
  13703. optionsChart = chart.options.chart,
  13704. type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
  13705. series,
  13706. Constr = seriesTypes[type];
  13707. // No such series type
  13708. if (!Constr) {
  13709. H.error(17, true);
  13710. }
  13711. series = new Constr();
  13712. series.init(this, options);
  13713. return series;
  13714. },
  13715. /**
  13716. * Order all series above a given index. When series are added and ordered
  13717. * by configuration, only the last series is handled (#248, #1123, #2456,
  13718. * #6112). This function is called on series initialization and destroy.
  13719. *
  13720. * @param {number} fromIndex - If this is given, only the series above this
  13721. * index are handled.
  13722. */
  13723. orderSeries: function(fromIndex) {
  13724. var series = this.series,
  13725. i = fromIndex || 0;
  13726. for (; i < series.length; i++) {
  13727. if (series[i]) {
  13728. series[i].index = i;
  13729. series[i].name = series[i].name ||
  13730. 'Series ' + (series[i].index + 1);
  13731. }
  13732. }
  13733. },
  13734. /**
  13735. * Check whether a given point is within the plot area
  13736. *
  13737. * @param {Number} plotX Pixel x relative to the plot area
  13738. * @param {Number} plotY Pixel y relative to the plot area
  13739. * @param {Boolean} inverted Whether the chart is inverted
  13740. */
  13741. isInsidePlot: function(plotX, plotY, inverted) {
  13742. var x = inverted ? plotY : plotX,
  13743. y = inverted ? plotX : plotY;
  13744. return x >= 0 &&
  13745. x <= this.plotWidth &&
  13746. y >= 0 &&
  13747. y <= this.plotHeight;
  13748. },
  13749. /**
  13750. * Redraw the chart after changes have been done to the data, axis extremes
  13751. * chart size or chart elements. All methods for updating axes, series or
  13752. * points have a parameter for redrawing the chart. This is `true` by
  13753. * default. But in many cases you want to do more than one operation on the
  13754. * chart before redrawing, for example add a number of points. In those
  13755. * cases it is a waste of resources to redraw the chart for each new point
  13756. * added. So you add the points and call `chart.redraw()` after.
  13757. *
  13758. * @param {AnimationOptions} animation
  13759. * If or how to apply animation to the redraw.
  13760. */
  13761. redraw: function(animation) {
  13762. var chart = this,
  13763. axes = chart.axes,
  13764. series = chart.series,
  13765. pointer = chart.pointer,
  13766. legend = chart.legend,
  13767. redrawLegend = chart.isDirtyLegend,
  13768. hasStackedSeries,
  13769. hasDirtyStacks,
  13770. hasCartesianSeries = chart.hasCartesianSeries,
  13771. isDirtyBox = chart.isDirtyBox,
  13772. i,
  13773. serie,
  13774. renderer = chart.renderer,
  13775. isHiddenChart = renderer.isHidden(),
  13776. afterRedraw = [];
  13777. // Handle responsive rules, not only on resize (#6130)
  13778. if (chart.setResponsive) {
  13779. chart.setResponsive(false);
  13780. }
  13781. H.setAnimation(animation, chart);
  13782. if (isHiddenChart) {
  13783. chart.temporaryDisplay();
  13784. }
  13785. // Adjust title layout (reflow multiline text)
  13786. chart.layOutTitles();
  13787. // link stacked series
  13788. i = series.length;
  13789. while (i--) {
  13790. serie = series[i];
  13791. if (serie.options.stacking) {
  13792. hasStackedSeries = true;
  13793. if (serie.isDirty) {
  13794. hasDirtyStacks = true;
  13795. break;
  13796. }
  13797. }
  13798. }
  13799. if (hasDirtyStacks) { // mark others as dirty
  13800. i = series.length;
  13801. while (i--) {
  13802. serie = series[i];
  13803. if (serie.options.stacking) {
  13804. serie.isDirty = true;
  13805. }
  13806. }
  13807. }
  13808. // Handle updated data in the series
  13809. each(series, function(serie) {
  13810. if (serie.isDirty) {
  13811. if (serie.options.legendType === 'point') {
  13812. if (serie.updateTotals) {
  13813. serie.updateTotals();
  13814. }
  13815. redrawLegend = true;
  13816. }
  13817. }
  13818. if (serie.isDirtyData) {
  13819. fireEvent(serie, 'updatedData');
  13820. }
  13821. });
  13822. // handle added or removed series
  13823. if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
  13824. // draw legend graphics
  13825. legend.render();
  13826. chart.isDirtyLegend = false;
  13827. }
  13828. // reset stacks
  13829. if (hasStackedSeries) {
  13830. chart.getStacks();
  13831. }
  13832. if (hasCartesianSeries) {
  13833. // set axes scales
  13834. each(axes, function(axis) {
  13835. axis.updateNames();
  13836. axis.setScale();
  13837. });
  13838. }
  13839. chart.getMargins(); // #3098
  13840. if (hasCartesianSeries) {
  13841. // If one axis is dirty, all axes must be redrawn (#792, #2169)
  13842. each(axes, function(axis) {
  13843. if (axis.isDirty) {
  13844. isDirtyBox = true;
  13845. }
  13846. });
  13847. // redraw axes
  13848. each(axes, function(axis) {
  13849. // Fire 'afterSetExtremes' only if extremes are set
  13850. var key = axis.min + ',' + axis.max;
  13851. if (axis.extKey !== key) { // #821, #4452
  13852. axis.extKey = key;
  13853. afterRedraw.push(function() { // prevent a recursive call to chart.redraw() (#1119)
  13854. fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751
  13855. delete axis.eventArgs;
  13856. });
  13857. }
  13858. if (isDirtyBox || hasStackedSeries) {
  13859. axis.redraw();
  13860. }
  13861. });
  13862. }
  13863. // the plot areas size has changed
  13864. if (isDirtyBox) {
  13865. chart.drawChartBox();
  13866. }
  13867. // Fire an event before redrawing series, used by the boost module to
  13868. // clear previous series renderings.
  13869. fireEvent(chart, 'predraw');
  13870. // redraw affected series
  13871. each(series, function(serie) {
  13872. if ((isDirtyBox || serie.isDirty) && serie.visible) {
  13873. serie.redraw();
  13874. }
  13875. // Set it here, otherwise we will have unlimited 'updatedData' calls
  13876. // for a hidden series after setData(). Fixes #6012
  13877. serie.isDirtyData = false;
  13878. });
  13879. // move tooltip or reset
  13880. if (pointer) {
  13881. pointer.reset(true);
  13882. }
  13883. // redraw if canvas
  13884. renderer.draw();
  13885. // Fire the events
  13886. fireEvent(chart, 'redraw');
  13887. fireEvent(chart, 'render');
  13888. if (isHiddenChart) {
  13889. chart.temporaryDisplay(true);
  13890. }
  13891. // Fire callbacks that are put on hold until after the redraw
  13892. each(afterRedraw, function(callback) {
  13893. callback.call();
  13894. });
  13895. },
  13896. /**
  13897. * Get an axis, series or point object by `id` as given in the configuration
  13898. * options. Returns `undefined` if no item is found.
  13899. * @param id {String} The id as given in the configuration options.
  13900. * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined}
  13901. * The retrieved item.
  13902. * @sample highcharts/plotoptions/series-id/
  13903. * Get series by id
  13904. */
  13905. get: function(id) {
  13906. var ret,
  13907. series = this.series,
  13908. i;
  13909. function itemById(item) {
  13910. return item.id === id || (item.options && item.options.id === id);
  13911. }
  13912. ret =
  13913. // Search axes
  13914. find(this.axes, itemById) ||
  13915. // Search series
  13916. find(this.series, itemById);
  13917. // Search points
  13918. for (i = 0; !ret && i < series.length; i++) {
  13919. ret = find(series[i].points || [], itemById);
  13920. }
  13921. return ret;
  13922. },
  13923. /**
  13924. * Create the Axis instances based on the config options
  13925. */
  13926. getAxes: function() {
  13927. var chart = this,
  13928. options = this.options,
  13929. xAxisOptions = options.xAxis = splat(options.xAxis || {}),
  13930. yAxisOptions = options.yAxis = splat(options.yAxis || {}),
  13931. optionsArray;
  13932. // make sure the options are arrays and add some members
  13933. each(xAxisOptions, function(axis, i) {
  13934. axis.index = i;
  13935. axis.isX = true;
  13936. });
  13937. each(yAxisOptions, function(axis, i) {
  13938. axis.index = i;
  13939. });
  13940. // concatenate all axis options into one array
  13941. optionsArray = xAxisOptions.concat(yAxisOptions);
  13942. each(optionsArray, function(axisOptions) {
  13943. new Axis(chart, axisOptions); // eslint-disable-line no-new
  13944. });
  13945. },
  13946. /**
  13947. * Returns an array of all currently selected points in the chart. Points
  13948. * can be selected by clicking or programmatically by the {@link
  13949. * Highcharts.Point#select} function.
  13950. *
  13951. * @return {Array.<Highcharts.Point>}
  13952. * The currently selected points.
  13953. *
  13954. * @sample highcharts/plotoptions/series-allowpointselect-line/
  13955. * Get selected points
  13956. */
  13957. getSelectedPoints: function() {
  13958. var points = [];
  13959. each(this.series, function(serie) {
  13960. // series.data - for points outside of viewed range (#6445)
  13961. points = points.concat(grep(serie.data || [], function(point) {
  13962. return point.selected;
  13963. }));
  13964. });
  13965. return points;
  13966. },
  13967. /**
  13968. * Returns an array of all currently selected series in the chart. Series
  13969. * can be selected either programmatically by the {@link
  13970. * Highcharts.Series#select} function or by checking the checkbox next to
  13971. * the legend item if {@link
  13972. * https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox|
  13973. * series.showCheckBox} is true.
  13974. *
  13975. * @return {Array.<Highcharts.Series>}
  13976. * The currently selected series.
  13977. *
  13978. * @sample highcharts/members/chart-getselectedseries/
  13979. * Get selected series
  13980. */
  13981. getSelectedSeries: function() {
  13982. return grep(this.series, function(serie) {
  13983. return serie.selected;
  13984. });
  13985. },
  13986. /**
  13987. * Set a new title or subtitle for the chart.
  13988. *
  13989. * @param titleOptions {TitleOptions}
  13990. * New title options.
  13991. * @param subtitleOptions {SubtitleOptions}
  13992. * New subtitle options.
  13993. * @param redraw {Boolean}
  13994. * Whether to redraw the chart or wait for a later call to
  13995. * `chart.redraw()`.
  13996. *
  13997. * @sample highcharts/members/chart-settitle/ Set title text and styles
  13998. *
  13999. */
  14000. setTitle: function(titleOptions, subtitleOptions, redraw) {
  14001. var chart = this,
  14002. options = chart.options,
  14003. chartTitleOptions,
  14004. chartSubtitleOptions;
  14005. chartTitleOptions = options.title = merge(
  14006. // Default styles
  14007. {
  14008. style: {
  14009. color: '#333333',
  14010. fontSize: options.isStock ? '16px' : '18px' // #2944
  14011. }
  14012. },
  14013. options.title,
  14014. titleOptions
  14015. );
  14016. chartSubtitleOptions = options.subtitle = merge(
  14017. // Default styles
  14018. {
  14019. style: {
  14020. color: '#666666'
  14021. }
  14022. },
  14023. options.subtitle,
  14024. subtitleOptions
  14025. );
  14026. // add title and subtitle
  14027. each([
  14028. ['title', titleOptions, chartTitleOptions],
  14029. ['subtitle', subtitleOptions, chartSubtitleOptions]
  14030. ], function(arr, i) {
  14031. var name = arr[0],
  14032. title = chart[name],
  14033. titleOptions = arr[1],
  14034. chartTitleOptions = arr[2];
  14035. if (title && titleOptions) {
  14036. chart[name] = title = title.destroy(); // remove old
  14037. }
  14038. if (chartTitleOptions && chartTitleOptions.text && !title) {
  14039. chart[name] = chart.renderer.text(
  14040. chartTitleOptions.text,
  14041. 0,
  14042. 0,
  14043. chartTitleOptions.useHTML
  14044. )
  14045. .attr({
  14046. align: chartTitleOptions.align,
  14047. 'class': 'highcharts-' + name,
  14048. zIndex: chartTitleOptions.zIndex || 4
  14049. })
  14050. .add();
  14051. // Update methods, shortcut to Chart.setTitle
  14052. chart[name].update = function(o) {
  14053. chart.setTitle(!i && o, i && o);
  14054. };
  14055. // Presentational
  14056. chart[name].css(chartTitleOptions.style);
  14057. }
  14058. });
  14059. chart.layOutTitles(redraw);
  14060. },
  14061. /**
  14062. * Lay out the chart titles and cache the full offset height for use
  14063. * in getMargins
  14064. */
  14065. layOutTitles: function(redraw) {
  14066. var titleOffset = 0,
  14067. requiresDirtyBox,
  14068. renderer = this.renderer,
  14069. spacingBox = this.spacingBox;
  14070. // Lay out the title and the subtitle respectively
  14071. each(['title', 'subtitle'], function(key) {
  14072. var title = this[key],
  14073. titleOptions = this.options[key],
  14074. offset = key === 'title' ? -3 :
  14075. // Floating subtitle (#6574)
  14076. titleOptions.verticalAlign ? 0 : titleOffset + 2,
  14077. titleSize;
  14078. if (title) {
  14079. titleSize = titleOptions.style.fontSize;
  14080. titleSize = renderer.fontMetrics(titleSize, title).b;
  14081. title
  14082. .css({
  14083. width: (titleOptions.width ||
  14084. spacingBox.width + titleOptions.widthAdjust) + 'px'
  14085. })
  14086. .align(extend({
  14087. y: offset + titleSize
  14088. }, titleOptions), false, 'spacingBox');
  14089. if (!titleOptions.floating && !titleOptions.verticalAlign) {
  14090. titleOffset = Math.ceil(
  14091. titleOffset +
  14092. // Skip the cache for HTML (#3481)
  14093. title.getBBox(titleOptions.useHTML).height
  14094. );
  14095. }
  14096. }
  14097. }, this);
  14098. requiresDirtyBox = this.titleOffset !== titleOffset;
  14099. this.titleOffset = titleOffset; // used in getMargins
  14100. if (!this.isDirtyBox && requiresDirtyBox) {
  14101. this.isDirtyBox = requiresDirtyBox;
  14102. // Redraw if necessary (#2719, #2744)
  14103. if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) {
  14104. this.redraw();
  14105. }
  14106. }
  14107. },
  14108. /**
  14109. * Get chart width and height according to options and container size
  14110. */
  14111. getChartSize: function() {
  14112. var chart = this,
  14113. optionsChart = chart.options.chart,
  14114. widthOption = optionsChart.width,
  14115. heightOption = optionsChart.height,
  14116. renderTo = chart.renderTo;
  14117. // Get inner width and height
  14118. if (!defined(widthOption)) {
  14119. chart.containerWidth = getStyle(renderTo, 'width');
  14120. }
  14121. if (!defined(heightOption)) {
  14122. chart.containerHeight = getStyle(renderTo, 'height');
  14123. }
  14124. chart.chartWidth = Math.max( // #1393
  14125. 0,
  14126. widthOption || chart.containerWidth || 600 // #1460
  14127. );
  14128. chart.chartHeight = Math.max(
  14129. 0,
  14130. H.relativeLength(
  14131. heightOption,
  14132. chart.chartWidth
  14133. ) || chart.containerHeight || 400
  14134. );
  14135. },
  14136. /**
  14137. * If the renderTo element has no offsetWidth, most likely one or more of
  14138. * its parents are hidden. Loop up the DOM tree to temporarily display the
  14139. * parents, then save the original display properties, and when the true
  14140. * size is retrieved, reset them. Used on first render and on redraws.
  14141. *
  14142. * @param {Boolean} revert - Revert to the saved original styles.
  14143. */
  14144. temporaryDisplay: function(revert) {
  14145. var node = this.renderTo,
  14146. tempStyle;
  14147. if (!revert) {
  14148. while (node && node.style) {
  14149. if (getStyle(node, 'display', false) === 'none') {
  14150. node.hcOrigStyle = {
  14151. display: node.style.display,
  14152. height: node.style.height,
  14153. overflow: node.style.overflow
  14154. };
  14155. tempStyle = {
  14156. display: 'block',
  14157. overflow: 'hidden'
  14158. };
  14159. if (node !== this.renderTo) {
  14160. tempStyle.height = 0;
  14161. }
  14162. H.css(node, tempStyle);
  14163. if (node.style.setProperty) { // #2631
  14164. node.style.setProperty('display', 'block', 'important');
  14165. }
  14166. }
  14167. node = node.parentNode;
  14168. }
  14169. } else {
  14170. while (node && node.style) {
  14171. if (node.hcOrigStyle) {
  14172. H.css(node, node.hcOrigStyle);
  14173. delete node.hcOrigStyle;
  14174. }
  14175. node = node.parentNode;
  14176. }
  14177. }
  14178. },
  14179. /**
  14180. * Setter for the chart class name
  14181. */
  14182. setClassName: function(className) {
  14183. this.container.className = 'highcharts-container ' + (className || '');
  14184. },
  14185. /**
  14186. * Get the containing element, determine the size and create the inner
  14187. * container div to hold the chart
  14188. */
  14189. getContainer: function() {
  14190. var chart = this,
  14191. container,
  14192. options = chart.options,
  14193. optionsChart = options.chart,
  14194. chartWidth,
  14195. chartHeight,
  14196. renderTo = chart.renderTo,
  14197. indexAttrName = 'data-highcharts-chart',
  14198. oldChartIndex,
  14199. Ren,
  14200. containerId = H.uniqueKey(),
  14201. containerStyle,
  14202. key;
  14203. if (!renderTo) {
  14204. chart.renderTo = renderTo = optionsChart.renderTo;
  14205. }
  14206. if (isString(renderTo)) {
  14207. chart.renderTo = renderTo = doc.getElementById(renderTo);
  14208. }
  14209. // Display an error if the renderTo is wrong
  14210. if (!renderTo) {
  14211. H.error(13, true);
  14212. }
  14213. // If the container already holds a chart, destroy it. The check for
  14214. // hasRendered is there because web pages that are saved to disk from
  14215. // the browser, will preserve the data-highcharts-chart attribute and
  14216. // the SVG contents, but not an interactive chart. So in this case,
  14217. // charts[oldChartIndex] will point to the wrong chart if any (#2609).
  14218. oldChartIndex = pInt(attr(renderTo, indexAttrName));
  14219. if (
  14220. isNumber(oldChartIndex) &&
  14221. charts[oldChartIndex] &&
  14222. charts[oldChartIndex].hasRendered
  14223. ) {
  14224. charts[oldChartIndex].destroy();
  14225. }
  14226. // Make a reference to the chart from the div
  14227. attr(renderTo, indexAttrName, chart.index);
  14228. // remove previous chart
  14229. renderTo.innerHTML = '';
  14230. // If the container doesn't have an offsetWidth, it has or is a child of
  14231. // a node that has display:none. We need to temporarily move it out to a
  14232. // visible state to determine the size, else the legend and tooltips
  14233. // won't render properly. The skipClone option is used in sparklines as
  14234. // a micro optimization, saving about 1-2 ms each chart.
  14235. if (!optionsChart.skipClone && !renderTo.offsetWidth) {
  14236. chart.temporaryDisplay();
  14237. }
  14238. // get the width and height
  14239. chart.getChartSize();
  14240. chartWidth = chart.chartWidth;
  14241. chartHeight = chart.chartHeight;
  14242. // Create the inner container
  14243. containerStyle = extend({
  14244. position: 'relative',
  14245. overflow: 'hidden', // needed for context menu (avoid scrollbars)
  14246. // and content overflow in IE
  14247. width: chartWidth + 'px',
  14248. height: chartHeight + 'px',
  14249. textAlign: 'left',
  14250. lineHeight: 'normal', // #427
  14251. zIndex: 0, // #1072
  14252. '-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
  14253. }, optionsChart.style);
  14254. /**
  14255. * The containing HTML element of the chart. The container is
  14256. * dynamically inserted into the element given as the `renderTo`
  14257. * parameterin the {@link Highcharts#chart} constructor.
  14258. *
  14259. * @memberOf Highcharts.Chart
  14260. * @type {HTMLDOMElement}
  14261. */
  14262. container = createElement(
  14263. 'div', {
  14264. id: containerId
  14265. },
  14266. containerStyle,
  14267. renderTo
  14268. );
  14269. chart.container = container;
  14270. // cache the cursor (#1650)
  14271. chart._cursor = container.style.cursor;
  14272. // Initialize the renderer
  14273. Ren = H[optionsChart.renderer] || Renderer;
  14274. chart.renderer = new Ren(
  14275. container,
  14276. chartWidth,
  14277. chartHeight,
  14278. null,
  14279. optionsChart.forExport,
  14280. options.exporting && options.exporting.allowHTML
  14281. );
  14282. chart.setClassName(optionsChart.className);
  14283. chart.renderer.setStyle(optionsChart.style);
  14284. // Add a reference to the charts index
  14285. chart.renderer.chartIndex = chart.index;
  14286. },
  14287. /**
  14288. * Calculate margins by rendering axis labels in a preliminary position.
  14289. * Title, subtitle and legend have already been rendered at this stage, but
  14290. * will be moved into their final positions
  14291. */
  14292. getMargins: function(skipAxes) {
  14293. var chart = this,
  14294. spacing = chart.spacing,
  14295. margin = chart.margin,
  14296. titleOffset = chart.titleOffset;
  14297. chart.resetMargins();
  14298. // Adjust for title and subtitle
  14299. if (titleOffset && !defined(margin[0])) {
  14300. chart.plotTop = Math.max(
  14301. chart.plotTop,
  14302. titleOffset + chart.options.title.margin + spacing[0]
  14303. );
  14304. }
  14305. // Adjust for legend
  14306. if (chart.legend.display) {
  14307. chart.legend.adjustMargins(margin, spacing);
  14308. }
  14309. // adjust for scroller
  14310. if (chart.extraMargin) {
  14311. chart[chart.extraMargin.type] =
  14312. (chart[chart.extraMargin.type] || 0) + chart.extraMargin.value;
  14313. }
  14314. if (chart.extraTopMargin) {
  14315. chart.plotTop += chart.extraTopMargin;
  14316. }
  14317. if (!skipAxes) {
  14318. this.getAxisMargins();
  14319. }
  14320. },
  14321. getAxisMargins: function() {
  14322. var chart = this,
  14323. // [top, right, bottom, left]
  14324. axisOffset = chart.axisOffset = [0, 0, 0, 0],
  14325. margin = chart.margin;
  14326. // pre-render axes to get labels offset width
  14327. if (chart.hasCartesianSeries) {
  14328. each(chart.axes, function(axis) {
  14329. if (axis.visible) {
  14330. axis.getOffset();
  14331. }
  14332. });
  14333. }
  14334. // Add the axis offsets
  14335. each(marginNames, function(m, side) {
  14336. if (!defined(margin[side])) {
  14337. chart[m] += axisOffset[side];
  14338. }
  14339. });
  14340. chart.setChartSize();
  14341. },
  14342. /**
  14343. * Reflows the chart to its container. By default, the chart reflows
  14344. * automatically to its container following a `window.resize` event, as per
  14345. * the {@link https://api.highcharts/highcharts/chart.reflow|chart.reflow}
  14346. * option. However, there are no reliable events for div resize, so if the
  14347. * container is resized without a window resize event, this must be called
  14348. * explicitly.
  14349. *
  14350. * @param {Object} e
  14351. * Event arguments. Used primarily when the function is called
  14352. * internally as a response to window resize.
  14353. *
  14354. * @sample highcharts/members/chart-reflow/
  14355. * Resize div and reflow
  14356. * @sample highcharts/chart/events-container/
  14357. * Pop up and reflow
  14358. */
  14359. reflow: function(e) {
  14360. var chart = this,
  14361. optionsChart = chart.options.chart,
  14362. renderTo = chart.renderTo,
  14363. hasUserWidth = defined(optionsChart.width),
  14364. width = optionsChart.width || getStyle(renderTo, 'width'),
  14365. height = optionsChart.height || getStyle(renderTo, 'height'),
  14366. target = e ? e.target : win;
  14367. // Width and height checks for display:none. Target is doc in IE8 and
  14368. // Opera, win in Firefox, Chrome and IE9.
  14369. if (!hasUserWidth &&
  14370. !chart.isPrinting &&
  14371. width &&
  14372. height &&
  14373. (target === win || target === doc)
  14374. ) {
  14375. if (
  14376. width !== chart.containerWidth ||
  14377. height !== chart.containerHeight
  14378. ) {
  14379. clearTimeout(chart.reflowTimeout);
  14380. // When called from window.resize, e is set, else it's called
  14381. // directly (#2224)
  14382. chart.reflowTimeout = syncTimeout(function() {
  14383. // Set size, it may have been destroyed in the meantime
  14384. // (#1257)
  14385. if (chart.container) {
  14386. chart.setSize(undefined, undefined, false);
  14387. }
  14388. }, e ? 100 : 0);
  14389. }
  14390. chart.containerWidth = width;
  14391. chart.containerHeight = height;
  14392. }
  14393. },
  14394. /**
  14395. * Add the event handlers necessary for auto resizing
  14396. */
  14397. initReflow: function() {
  14398. var chart = this,
  14399. unbind;
  14400. unbind = addEvent(win, 'resize', function(e) {
  14401. chart.reflow(e);
  14402. });
  14403. addEvent(chart, 'destroy', unbind);
  14404. // The following will add listeners to re-fit the chart before and after
  14405. // printing (#2284). However it only works in WebKit. Should have worked
  14406. // in Firefox, but not supported in IE.
  14407. /*
  14408. if (win.matchMedia) {
  14409. win.matchMedia('print').addListener(function reflow() {
  14410. chart.reflow();
  14411. });
  14412. }
  14413. */
  14414. },
  14415. /**
  14416. * Resize the chart to a given width and height. In order to set the width
  14417. * only, the height argument may be skipped. To set the height only, pass
  14418. * `undefined for the width.
  14419. * @param {Number|undefined|null} [width]
  14420. * The new pixel width of the chart. Since v4.2.6, the argument can
  14421. * be `undefined` in order to preserve the current value (when
  14422. * setting height only), or `null` to adapt to the width of the
  14423. * containing element.
  14424. * @param {Number|undefined|null} [height]
  14425. * The new pixel height of the chart. Since v4.2.6, the argument can
  14426. * be `undefined` in order to preserve the current value, or `null`
  14427. * in order to adapt to the height of the containing element.
  14428. * @param {AnimationOptions} [animation=true]
  14429. * Whether and how to apply animation.
  14430. *
  14431. * @sample highcharts/members/chart-setsize-button/
  14432. * Test resizing from buttons
  14433. * @sample highcharts/members/chart-setsize-jquery-resizable/
  14434. * Add a jQuery UI resizable
  14435. * @sample stock/members/chart-setsize/
  14436. * Highstock with UI resizable
  14437. */
  14438. setSize: function(width, height, animation) {
  14439. var chart = this,
  14440. renderer = chart.renderer,
  14441. globalAnimation;
  14442. // Handle the isResizing counter
  14443. chart.isResizing += 1;
  14444. // set the animation for the current process
  14445. H.setAnimation(animation, chart);
  14446. chart.oldChartHeight = chart.chartHeight;
  14447. chart.oldChartWidth = chart.chartWidth;
  14448. if (width !== undefined) {
  14449. chart.options.chart.width = width;
  14450. }
  14451. if (height !== undefined) {
  14452. chart.options.chart.height = height;
  14453. }
  14454. chart.getChartSize();
  14455. // Resize the container with the global animation applied if enabled
  14456. // (#2503)
  14457. globalAnimation = renderer.globalAnimation;
  14458. (globalAnimation ? animate : css)(chart.container, {
  14459. width: chart.chartWidth + 'px',
  14460. height: chart.chartHeight + 'px'
  14461. }, globalAnimation);
  14462. chart.setChartSize(true);
  14463. renderer.setSize(chart.chartWidth, chart.chartHeight, animation);
  14464. // handle axes
  14465. each(chart.axes, function(axis) {
  14466. axis.isDirty = true;
  14467. axis.setScale();
  14468. });
  14469. chart.isDirtyLegend = true; // force legend redraw
  14470. chart.isDirtyBox = true; // force redraw of plot and chart border
  14471. chart.layOutTitles(); // #2857
  14472. chart.getMargins();
  14473. chart.redraw(animation);
  14474. chart.oldChartHeight = null;
  14475. fireEvent(chart, 'resize');
  14476. // Fire endResize and set isResizing back. If animation is disabled,
  14477. // fire without delay
  14478. syncTimeout(function() {
  14479. if (chart) {
  14480. fireEvent(chart, 'endResize', null, function() {
  14481. chart.isResizing -= 1;
  14482. });
  14483. }
  14484. }, animObject(globalAnimation).duration);
  14485. },
  14486. /**
  14487. * Set the public chart properties. This is done before and after the
  14488. * pre-render to determine margin sizes
  14489. */
  14490. setChartSize: function(skipAxes) {
  14491. var chart = this,
  14492. inverted = chart.inverted,
  14493. renderer = chart.renderer,
  14494. chartWidth = chart.chartWidth,
  14495. chartHeight = chart.chartHeight,
  14496. optionsChart = chart.options.chart,
  14497. spacing = chart.spacing,
  14498. clipOffset = chart.clipOffset,
  14499. clipX,
  14500. clipY,
  14501. plotLeft,
  14502. plotTop,
  14503. plotWidth,
  14504. plotHeight,
  14505. plotBorderWidth;
  14506. function clipOffsetSide(side) {
  14507. var offset = clipOffset[side] || 0;
  14508. return Math.max(plotBorderWidth || offset, offset) / 2;
  14509. }
  14510. chart.plotLeft = plotLeft = Math.round(chart.plotLeft);
  14511. chart.plotTop = plotTop = Math.round(chart.plotTop);
  14512. chart.plotWidth = plotWidth = Math.max(
  14513. 0,
  14514. Math.round(chartWidth - plotLeft - chart.marginRight)
  14515. );
  14516. chart.plotHeight = plotHeight = Math.max(
  14517. 0,
  14518. Math.round(chartHeight - plotTop - chart.marginBottom)
  14519. );
  14520. chart.plotSizeX = inverted ? plotHeight : plotWidth;
  14521. chart.plotSizeY = inverted ? plotWidth : plotHeight;
  14522. chart.plotBorderWidth = optionsChart.plotBorderWidth || 0;
  14523. // Set boxes used for alignment
  14524. chart.spacingBox = renderer.spacingBox = {
  14525. x: spacing[3],
  14526. y: spacing[0],
  14527. width: chartWidth - spacing[3] - spacing[1],
  14528. height: chartHeight - spacing[0] - spacing[2]
  14529. };
  14530. chart.plotBox = renderer.plotBox = {
  14531. x: plotLeft,
  14532. y: plotTop,
  14533. width: plotWidth,
  14534. height: plotHeight
  14535. };
  14536. plotBorderWidth = 2 * Math.floor(chart.plotBorderWidth / 2);
  14537. clipX = Math.ceil(clipOffsetSide(3));
  14538. clipY = Math.ceil(clipOffsetSide(0));
  14539. chart.clipBox = {
  14540. x: clipX,
  14541. y: clipY,
  14542. width: Math.floor(
  14543. chart.plotSizeX -
  14544. clipOffsetSide(1) -
  14545. clipX
  14546. ),
  14547. height: Math.max(
  14548. 0,
  14549. Math.floor(
  14550. chart.plotSizeY -
  14551. clipOffsetSide(2) -
  14552. clipY
  14553. )
  14554. )
  14555. };
  14556. if (!skipAxes) {
  14557. each(chart.axes, function(axis) {
  14558. axis.setAxisSize();
  14559. axis.setAxisTranslation();
  14560. });
  14561. }
  14562. },
  14563. /**
  14564. * Initial margins before auto size margins are applied
  14565. */
  14566. resetMargins: function() {
  14567. var chart = this,
  14568. chartOptions = chart.options.chart;
  14569. // Create margin and spacing array
  14570. each(['margin', 'spacing'], function splashArrays(target) {
  14571. var value = chartOptions[target],
  14572. values = isObject(value) ? value : [value, value, value, value];
  14573. each(['Top', 'Right', 'Bottom', 'Left'], function(sideName, side) {
  14574. chart[target][side] = pick(
  14575. chartOptions[target + sideName],
  14576. values[side]
  14577. );
  14578. });
  14579. });
  14580. // Set margin names like chart.plotTop, chart.plotLeft,
  14581. // chart.marginRight, chart.marginBottom.
  14582. each(marginNames, function(m, side) {
  14583. chart[m] = pick(chart.margin[side], chart.spacing[side]);
  14584. });
  14585. chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
  14586. chart.clipOffset = [];
  14587. },
  14588. /**
  14589. * Draw the borders and backgrounds for chart and plot area
  14590. */
  14591. drawChartBox: function() {
  14592. var chart = this,
  14593. optionsChart = chart.options.chart,
  14594. renderer = chart.renderer,
  14595. chartWidth = chart.chartWidth,
  14596. chartHeight = chart.chartHeight,
  14597. chartBackground = chart.chartBackground,
  14598. plotBackground = chart.plotBackground,
  14599. plotBorder = chart.plotBorder,
  14600. chartBorderWidth,
  14601. plotBGImage = chart.plotBGImage,
  14602. chartBackgroundColor = optionsChart.backgroundColor,
  14603. plotBackgroundColor = optionsChart.plotBackgroundColor,
  14604. plotBackgroundImage = optionsChart.plotBackgroundImage,
  14605. mgn,
  14606. bgAttr,
  14607. plotLeft = chart.plotLeft,
  14608. plotTop = chart.plotTop,
  14609. plotWidth = chart.plotWidth,
  14610. plotHeight = chart.plotHeight,
  14611. plotBox = chart.plotBox,
  14612. clipRect = chart.clipRect,
  14613. clipBox = chart.clipBox,
  14614. verb = 'animate';
  14615. // Chart area
  14616. if (!chartBackground) {
  14617. chart.chartBackground = chartBackground = renderer.rect()
  14618. .addClass('highcharts-background')
  14619. .add();
  14620. verb = 'attr';
  14621. }
  14622. // Presentational
  14623. chartBorderWidth = optionsChart.borderWidth || 0;
  14624. mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
  14625. bgAttr = {
  14626. fill: chartBackgroundColor || 'none'
  14627. };
  14628. if (chartBorderWidth || chartBackground['stroke-width']) { // #980
  14629. bgAttr.stroke = optionsChart.borderColor;
  14630. bgAttr['stroke-width'] = chartBorderWidth;
  14631. }
  14632. chartBackground
  14633. .attr(bgAttr)
  14634. .shadow(optionsChart.shadow);
  14635. chartBackground[verb]({
  14636. x: mgn / 2,
  14637. y: mgn / 2,
  14638. width: chartWidth - mgn - chartBorderWidth % 2,
  14639. height: chartHeight - mgn - chartBorderWidth % 2,
  14640. r: optionsChart.borderRadius
  14641. });
  14642. // Plot background
  14643. verb = 'animate';
  14644. if (!plotBackground) {
  14645. verb = 'attr';
  14646. chart.plotBackground = plotBackground = renderer.rect()
  14647. .addClass('highcharts-plot-background')
  14648. .add();
  14649. }
  14650. plotBackground[verb](plotBox);
  14651. // Presentational attributes for the background
  14652. plotBackground
  14653. .attr({
  14654. fill: plotBackgroundColor || 'none'
  14655. })
  14656. .shadow(optionsChart.plotShadow);
  14657. // Create the background image
  14658. if (plotBackgroundImage) {
  14659. if (!plotBGImage) {
  14660. chart.plotBGImage = renderer.image(
  14661. plotBackgroundImage,
  14662. plotLeft,
  14663. plotTop,
  14664. plotWidth,
  14665. plotHeight
  14666. ).add();
  14667. } else {
  14668. plotBGImage.animate(plotBox);
  14669. }
  14670. }
  14671. // Plot clip
  14672. if (!clipRect) {
  14673. chart.clipRect = renderer.clipRect(clipBox);
  14674. } else {
  14675. clipRect.animate({
  14676. width: clipBox.width,
  14677. height: clipBox.height
  14678. });
  14679. }
  14680. // Plot area border
  14681. verb = 'animate';
  14682. if (!plotBorder) {
  14683. verb = 'attr';
  14684. chart.plotBorder = plotBorder = renderer.rect()
  14685. .addClass('highcharts-plot-border')
  14686. .attr({
  14687. zIndex: 1 // Above the grid
  14688. })
  14689. .add();
  14690. }
  14691. // Presentational
  14692. plotBorder.attr({
  14693. stroke: optionsChart.plotBorderColor,
  14694. 'stroke-width': optionsChart.plotBorderWidth || 0,
  14695. fill: 'none'
  14696. });
  14697. plotBorder[verb](plotBorder.crisp({
  14698. x: plotLeft,
  14699. y: plotTop,
  14700. width: plotWidth,
  14701. height: plotHeight
  14702. }, -plotBorder.strokeWidth())); //#3282 plotBorder should be negative;
  14703. // reset
  14704. chart.isDirtyBox = false;
  14705. },
  14706. /**
  14707. * Detect whether a certain chart property is needed based on inspecting its
  14708. * options and series. This mainly applies to the chart.inverted property,
  14709. * and in extensions to the chart.angular and chart.polar properties.
  14710. */
  14711. propFromSeries: function() {
  14712. var chart = this,
  14713. optionsChart = chart.options.chart,
  14714. klass,
  14715. seriesOptions = chart.options.series,
  14716. i,
  14717. value;
  14718. each(['inverted', 'angular', 'polar'], function(key) {
  14719. // The default series type's class
  14720. klass = seriesTypes[optionsChart.type ||
  14721. optionsChart.defaultSeriesType];
  14722. // Get the value from available chart-wide properties
  14723. value =
  14724. optionsChart[key] || // It is set in the options
  14725. (klass && klass.prototype[key]); // The default series class
  14726. // requires it
  14727. // 4. Check if any the chart's series require it
  14728. i = seriesOptions && seriesOptions.length;
  14729. while (!value && i--) {
  14730. klass = seriesTypes[seriesOptions[i].type];
  14731. if (klass && klass.prototype[key]) {
  14732. value = true;
  14733. }
  14734. }
  14735. // Set the chart property
  14736. chart[key] = value;
  14737. });
  14738. },
  14739. /**
  14740. * Link two or more series together. This is done initially from
  14741. * Chart.render, and after Chart.addSeries and Series.remove.
  14742. */
  14743. linkSeries: function() {
  14744. var chart = this,
  14745. chartSeries = chart.series;
  14746. // Reset links
  14747. each(chartSeries, function(series) {
  14748. series.linkedSeries.length = 0;
  14749. });
  14750. // Apply new links
  14751. each(chartSeries, function(series) {
  14752. var linkedTo = series.options.linkedTo;
  14753. if (isString(linkedTo)) {
  14754. if (linkedTo === ':previous') {
  14755. linkedTo = chart.series[series.index - 1];
  14756. } else {
  14757. linkedTo = chart.get(linkedTo);
  14758. }
  14759. // #3341 avoid mutual linking
  14760. if (linkedTo && linkedTo.linkedParent !== series) {
  14761. linkedTo.linkedSeries.push(series);
  14762. series.linkedParent = linkedTo;
  14763. series.visible = pick(
  14764. series.options.visible,
  14765. linkedTo.options.visible,
  14766. series.visible
  14767. ); // #3879
  14768. }
  14769. }
  14770. });
  14771. },
  14772. /**
  14773. * Render series for the chart
  14774. */
  14775. renderSeries: function() {
  14776. each(this.series, function(serie) {
  14777. serie.translate();
  14778. serie.render();
  14779. });
  14780. },
  14781. /**
  14782. * Render labels for the chart
  14783. */
  14784. renderLabels: function() {
  14785. var chart = this,
  14786. labels = chart.options.labels;
  14787. if (labels.items) {
  14788. each(labels.items, function(label) {
  14789. var style = extend(labels.style, label.style),
  14790. x = pInt(style.left) + chart.plotLeft,
  14791. y = pInt(style.top) + chart.plotTop + 12;
  14792. // delete to prevent rewriting in IE
  14793. delete style.left;
  14794. delete style.top;
  14795. chart.renderer.text(
  14796. label.html,
  14797. x,
  14798. y
  14799. )
  14800. .attr({
  14801. zIndex: 2
  14802. })
  14803. .css(style)
  14804. .add();
  14805. });
  14806. }
  14807. },
  14808. /**
  14809. * Render all graphics for the chart
  14810. */
  14811. render: function() {
  14812. var chart = this,
  14813. axes = chart.axes,
  14814. renderer = chart.renderer,
  14815. options = chart.options,
  14816. tempWidth,
  14817. tempHeight,
  14818. redoHorizontal,
  14819. redoVertical;
  14820. // Title
  14821. chart.setTitle();
  14822. // Legend
  14823. chart.legend = new Legend(chart, options.legend);
  14824. // Get stacks
  14825. if (chart.getStacks) {
  14826. chart.getStacks();
  14827. }
  14828. // Get chart margins
  14829. chart.getMargins(true);
  14830. chart.setChartSize();
  14831. // Record preliminary dimensions for later comparison
  14832. tempWidth = chart.plotWidth;
  14833. tempHeight = chart.plotHeight = chart.plotHeight - 21; // 21 is the most common correction for X axis labels
  14834. // Get margins by pre-rendering axes
  14835. each(axes, function(axis) {
  14836. axis.setScale();
  14837. });
  14838. chart.getAxisMargins();
  14839. // If the plot area size has changed significantly, calculate tick positions again
  14840. redoHorizontal = tempWidth / chart.plotWidth > 1.1;
  14841. redoVertical = tempHeight / chart.plotHeight > 1.05; // Height is more sensitive
  14842. if (redoHorizontal || redoVertical) {
  14843. each(axes, function(axis) {
  14844. if ((axis.horiz && redoHorizontal) || (!axis.horiz && redoVertical)) {
  14845. axis.setTickInterval(true); // update to reflect the new margins
  14846. }
  14847. });
  14848. chart.getMargins(); // second pass to check for new labels
  14849. }
  14850. // Draw the borders and backgrounds
  14851. chart.drawChartBox();
  14852. // Axes
  14853. if (chart.hasCartesianSeries) {
  14854. each(axes, function(axis) {
  14855. if (axis.visible) {
  14856. axis.render();
  14857. }
  14858. });
  14859. }
  14860. // The series
  14861. if (!chart.seriesGroup) {
  14862. chart.seriesGroup = renderer.g('series-group')
  14863. .attr({
  14864. zIndex: 3
  14865. })
  14866. .add();
  14867. }
  14868. chart.renderSeries();
  14869. // Labels
  14870. chart.renderLabels();
  14871. // Credits
  14872. chart.addCredits();
  14873. // Handle responsiveness
  14874. if (chart.setResponsive) {
  14875. chart.setResponsive();
  14876. }
  14877. // Set flag
  14878. chart.hasRendered = true;
  14879. },
  14880. /**
  14881. * Set a new credits label for the chart.
  14882. *
  14883. * @param {CreditOptions} options
  14884. * A configuration object for the new credits.
  14885. * @sample highcharts/credits/credits-update/ Add and update credits
  14886. */
  14887. addCredits: function(credits) {
  14888. var chart = this;
  14889. credits = merge(true, this.options.credits, credits);
  14890. if (credits.enabled && !this.credits) {
  14891. /**
  14892. * The chart's credits label. The label has an `update` method that
  14893. * allows setting new options as per the {@link
  14894. * https://api.highcharts.com/highcharts/credits|
  14895. * credits options set}.
  14896. *
  14897. * @memberof Highcharts.Chart
  14898. * @name credits
  14899. * @type {Highcharts.SVGElement}
  14900. */
  14901. this.credits = this.renderer.text(
  14902. credits.text + (this.mapCredits || ''),
  14903. 0,
  14904. 0
  14905. )
  14906. .addClass('highcharts-credits')
  14907. .on('click', function() {
  14908. if (credits.href) {
  14909. win.location.href = credits.href;
  14910. }
  14911. })
  14912. .attr({
  14913. align: credits.position.align,
  14914. zIndex: 8
  14915. })
  14916. .css(credits.style)
  14917. .add()
  14918. .align(credits.position);
  14919. // Dynamically update
  14920. this.credits.update = function(options) {
  14921. chart.credits = chart.credits.destroy();
  14922. chart.addCredits(options);
  14923. };
  14924. }
  14925. },
  14926. /**
  14927. * Remove the chart and purge memory. This method is called internally
  14928. * before adding a second chart into the same container, as well as on
  14929. * window unload to prevent leaks.
  14930. *
  14931. * @sample highcharts/members/chart-destroy/
  14932. * Destroy the chart from a button
  14933. * @sample stock/members/chart-destroy/
  14934. * Destroy with Highstock
  14935. */
  14936. destroy: function() {
  14937. var chart = this,
  14938. axes = chart.axes,
  14939. series = chart.series,
  14940. container = chart.container,
  14941. i,
  14942. parentNode = container && container.parentNode;
  14943. // fire the chart.destoy event
  14944. fireEvent(chart, 'destroy');
  14945. // Delete the chart from charts lookup array
  14946. if (chart.renderer.forExport) {
  14947. H.erase(charts, chart); // #6569
  14948. } else {
  14949. charts[chart.index] = undefined;
  14950. }
  14951. H.chartCount--;
  14952. chart.renderTo.removeAttribute('data-highcharts-chart');
  14953. // remove events
  14954. removeEvent(chart);
  14955. // ==== Destroy collections:
  14956. // Destroy axes
  14957. i = axes.length;
  14958. while (i--) {
  14959. axes[i] = axes[i].destroy();
  14960. }
  14961. // Destroy scroller & scroller series before destroying base series
  14962. if (this.scroller && this.scroller.destroy) {
  14963. this.scroller.destroy();
  14964. }
  14965. // Destroy each series
  14966. i = series.length;
  14967. while (i--) {
  14968. series[i] = series[i].destroy();
  14969. }
  14970. // ==== Destroy chart properties:
  14971. each([
  14972. 'title', 'subtitle', 'chartBackground', 'plotBackground',
  14973. 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits',
  14974. 'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip',
  14975. 'renderer'
  14976. ], function(name) {
  14977. var prop = chart[name];
  14978. if (prop && prop.destroy) {
  14979. chart[name] = prop.destroy();
  14980. }
  14981. });
  14982. // remove container and all SVG
  14983. if (container) { // can break in IE when destroyed before finished loading
  14984. container.innerHTML = '';
  14985. removeEvent(container);
  14986. if (parentNode) {
  14987. discardElement(container);
  14988. }
  14989. }
  14990. // clean it all up
  14991. objectEach(chart, function(val, key) {
  14992. delete chart[key];
  14993. });
  14994. },
  14995. /**
  14996. * VML namespaces can't be added until after complete. Listening
  14997. * for Perini's doScroll hack is not enough.
  14998. */
  14999. isReadyToRender: function() {
  15000. var chart = this;
  15001. // Note: win == win.top is required
  15002. if ((!svg && (win == win.top && doc.readyState !== 'complete'))) { // eslint-disable-line eqeqeq
  15003. doc.attachEvent('onreadystatechange', function() {
  15004. doc.detachEvent('onreadystatechange', chart.firstRender);
  15005. if (doc.readyState === 'complete') {
  15006. chart.firstRender();
  15007. }
  15008. });
  15009. return false;
  15010. }
  15011. return true;
  15012. },
  15013. /**
  15014. * Prepare for first rendering after all data are loaded
  15015. */
  15016. firstRender: function() {
  15017. var chart = this,
  15018. options = chart.options;
  15019. // Check whether the chart is ready to render
  15020. if (!chart.isReadyToRender()) {
  15021. return;
  15022. }
  15023. // Create the container
  15024. chart.getContainer();
  15025. // Run an early event after the container and renderer are established
  15026. fireEvent(chart, 'init');
  15027. chart.resetMargins();
  15028. chart.setChartSize();
  15029. // Set the common chart properties (mainly invert) from the given series
  15030. chart.propFromSeries();
  15031. // get axes
  15032. chart.getAxes();
  15033. // Initialize the series
  15034. each(options.series || [], function(serieOptions) {
  15035. chart.initSeries(serieOptions);
  15036. });
  15037. chart.linkSeries();
  15038. // Run an event after axes and series are initialized, but before render. At this stage,
  15039. // the series data is indexed and cached in the xData and yData arrays, so we can access
  15040. // those before rendering. Used in Highstock.
  15041. fireEvent(chart, 'beforeRender');
  15042. // depends on inverted and on margins being set
  15043. if (Pointer) {
  15044. chart.pointer = new Pointer(chart, options);
  15045. }
  15046. chart.render();
  15047. // Fire the load event if there are no external images
  15048. if (!chart.renderer.imgCount && chart.onload) {
  15049. chart.onload();
  15050. }
  15051. // If the chart was rendered outside the top container, put it back in (#3679)
  15052. chart.temporaryDisplay(true);
  15053. },
  15054. /**
  15055. * On chart load
  15056. */
  15057. onload: function() {
  15058. // Run callbacks
  15059. each([this.callback].concat(this.callbacks), function(fn) {
  15060. if (fn && this.index !== undefined) { // Chart destroyed in its own callback (#3600)
  15061. fn.apply(this, [this]);
  15062. }
  15063. }, this);
  15064. fireEvent(this, 'load');
  15065. fireEvent(this, 'render');
  15066. // Set up auto resize, check for not destroyed (#6068)
  15067. if (defined(this.index) && this.options.chart.reflow !== false) {
  15068. this.initReflow();
  15069. }
  15070. // Don't run again
  15071. this.onload = null;
  15072. }
  15073. }); // end Chart
  15074. }(Highcharts));
  15075. (function(Highcharts) {
  15076. /**
  15077. * (c) 2010-2017 Torstein Honsi
  15078. *
  15079. * License: www.highcharts.com/license
  15080. */
  15081. var Point,
  15082. H = Highcharts,
  15083. each = H.each,
  15084. extend = H.extend,
  15085. erase = H.erase,
  15086. fireEvent = H.fireEvent,
  15087. format = H.format,
  15088. isArray = H.isArray,
  15089. isNumber = H.isNumber,
  15090. pick = H.pick,
  15091. removeEvent = H.removeEvent;
  15092. /**
  15093. * The Point object. The point objects are generated from the `series.data`
  15094. * configuration objects or raw numbers. They can be accessed from the
  15095. * `Series.points` array. Other ways to instaniate points are through {@link
  15096. * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}.
  15097. *
  15098. * @class
  15099. */
  15100. Highcharts.Point = Point = function() {};
  15101. Highcharts.Point.prototype = {
  15102. /**
  15103. * Initialize the point. Called internally based on the series.data option.
  15104. * @param {Object} series The series object containing this point.
  15105. * @param {Object} options The data in either number, array or object
  15106. * format.
  15107. * @param {Number} x Optionally, the X value of the.
  15108. * @returns {Object} The Point instance.
  15109. */
  15110. init: function(series, options, x) {
  15111. var point = this,
  15112. colors,
  15113. colorCount = series.chart.options.chart.colorCount,
  15114. colorIndex;
  15115. /**
  15116. * The series object associated with the point.
  15117. *
  15118. * @name series
  15119. * @memberof Highcharts.Point
  15120. * @type Highcharts.Series
  15121. */
  15122. point.series = series;
  15123. /**
  15124. * The point's current color.
  15125. * @name color
  15126. * @memberof Highcharts.Point
  15127. * @type {Color}
  15128. */
  15129. point.color = series.color; // #3445
  15130. point.applyOptions(options, x);
  15131. if (series.options.colorByPoint) {
  15132. colors = series.options.colors || series.chart.options.colors;
  15133. point.color = point.color || colors[series.colorCounter];
  15134. colorCount = colors.length;
  15135. colorIndex = series.colorCounter;
  15136. series.colorCounter++;
  15137. // loop back to zero
  15138. if (series.colorCounter === colorCount) {
  15139. series.colorCounter = 0;
  15140. }
  15141. } else {
  15142. colorIndex = series.colorIndex;
  15143. }
  15144. point.colorIndex = pick(point.colorIndex, colorIndex);
  15145. series.chart.pointCount++;
  15146. return point;
  15147. },
  15148. /**
  15149. * Apply the options containing the x and y data and possible some extra
  15150. * properties. Called on point init or from point.update.
  15151. *
  15152. * @param {Object} options The point options as defined in series.data.
  15153. * @param {Number} x Optionally, the X value.
  15154. * @returns {Object} The Point instance.
  15155. */
  15156. applyOptions: function(options, x) {
  15157. var point = this,
  15158. series = point.series,
  15159. pointValKey = series.options.pointValKey || series.pointValKey;
  15160. options = Point.prototype.optionsToObject.call(this, options);
  15161. // copy options directly to point
  15162. extend(point, options);
  15163. point.options = point.options ? extend(point.options, options) : options;
  15164. // Since options are copied into the Point instance, some accidental options must be shielded (#5681)
  15165. if (options.group) {
  15166. delete point.group;
  15167. }
  15168. // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
  15169. if (pointValKey) {
  15170. point.y = point[pointValKey];
  15171. }
  15172. point.isNull = pick(
  15173. point.isValid && !point.isValid(),
  15174. point.x === null || !isNumber(point.y, true)
  15175. ); // #3571, check for NaN
  15176. // The point is initially selected by options (#5777)
  15177. if (point.selected) {
  15178. point.state = 'select';
  15179. }
  15180. // If no x is set by now, get auto incremented value. All points must have an
  15181. // x value, however the y value can be null to create a gap in the series
  15182. if ('name' in point && x === undefined && series.xAxis && series.xAxis.hasNames) {
  15183. point.x = series.xAxis.nameToX(point);
  15184. }
  15185. if (point.x === undefined && series) {
  15186. if (x === undefined) {
  15187. point.x = series.autoIncrement(point);
  15188. } else {
  15189. point.x = x;
  15190. }
  15191. }
  15192. return point;
  15193. },
  15194. /**
  15195. * Transform number or array configs into objects
  15196. */
  15197. optionsToObject: function(options) {
  15198. var ret = {},
  15199. series = this.series,
  15200. keys = series.options.keys,
  15201. pointArrayMap = keys || series.pointArrayMap || ['y'],
  15202. valueCount = pointArrayMap.length,
  15203. firstItemType,
  15204. i = 0,
  15205. j = 0;
  15206. if (isNumber(options) || options === null) {
  15207. ret[pointArrayMap[0]] = options;
  15208. } else if (isArray(options)) {
  15209. // with leading x value
  15210. if (!keys && options.length > valueCount) {
  15211. firstItemType = typeof options[0];
  15212. if (firstItemType === 'string') {
  15213. ret.name = options[0];
  15214. } else if (firstItemType === 'number') {
  15215. ret.x = options[0];
  15216. }
  15217. i++;
  15218. }
  15219. while (j < valueCount) {
  15220. if (!keys || options[i] !== undefined) { // Skip undefined positions for keys
  15221. ret[pointArrayMap[j]] = options[i];
  15222. }
  15223. i++;
  15224. j++;
  15225. }
  15226. } else if (typeof options === 'object') {
  15227. ret = options;
  15228. // This is the fastest way to detect if there are individual point dataLabels that need
  15229. // to be considered in drawDataLabels. These can only occur in object configs.
  15230. if (options.dataLabels) {
  15231. series._hasPointLabels = true;
  15232. }
  15233. // Same approach as above for markers
  15234. if (options.marker) {
  15235. series._hasPointMarkers = true;
  15236. }
  15237. }
  15238. return ret;
  15239. },
  15240. /**
  15241. * Get the CSS class names for individual points
  15242. * @returns {String} The class name
  15243. */
  15244. getClassName: function() {
  15245. return 'highcharts-point' +
  15246. (this.selected ? ' highcharts-point-select' : '') +
  15247. (this.negative ? ' highcharts-negative' : '') +
  15248. (this.isNull ? ' highcharts-null-point' : '') +
  15249. (this.colorIndex !== undefined ? ' highcharts-color-' +
  15250. this.colorIndex : '') +
  15251. (this.options.className ? ' ' + this.options.className : '') +
  15252. (this.zone && this.zone.className ? ' ' +
  15253. this.zone.className.replace('highcharts-negative', '') : '');
  15254. },
  15255. /**
  15256. * Return the zone that the point belongs to
  15257. */
  15258. getZone: function() {
  15259. var series = this.series,
  15260. zones = series.zones,
  15261. zoneAxis = series.zoneAxis || 'y',
  15262. i = 0,
  15263. zone;
  15264. zone = zones[i];
  15265. while (this[zoneAxis] >= zone.value) {
  15266. zone = zones[++i];
  15267. }
  15268. if (zone && zone.color && !this.options.color) {
  15269. this.color = zone.color;
  15270. }
  15271. return zone;
  15272. },
  15273. /**
  15274. * Destroy a point to clear memory. Its reference still stays in series.data.
  15275. */
  15276. destroy: function() {
  15277. var point = this,
  15278. series = point.series,
  15279. chart = series.chart,
  15280. hoverPoints = chart.hoverPoints,
  15281. prop;
  15282. chart.pointCount--;
  15283. if (hoverPoints) {
  15284. point.setState();
  15285. erase(hoverPoints, point);
  15286. if (!hoverPoints.length) {
  15287. chart.hoverPoints = null;
  15288. }
  15289. }
  15290. if (point === chart.hoverPoint) {
  15291. point.onMouseOut();
  15292. }
  15293. // remove all events
  15294. if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
  15295. removeEvent(point);
  15296. point.destroyElements();
  15297. }
  15298. if (point.legendItem) { // pies have legend items
  15299. chart.legend.destroyItem(point);
  15300. }
  15301. for (prop in point) {
  15302. point[prop] = null;
  15303. }
  15304. },
  15305. /**
  15306. * Destroy SVG elements associated with the point
  15307. */
  15308. destroyElements: function() {
  15309. var point = this,
  15310. props = ['graphic', 'dataLabel', 'dataLabelUpper', 'connector', 'shadowGroup'],
  15311. prop,
  15312. i = 6;
  15313. while (i--) {
  15314. prop = props[i];
  15315. if (point[prop]) {
  15316. point[prop] = point[prop].destroy();
  15317. }
  15318. }
  15319. },
  15320. /**
  15321. * Return the configuration hash needed for the data label and tooltip formatters
  15322. */
  15323. getLabelConfig: function() {
  15324. return {
  15325. x: this.category,
  15326. y: this.y,
  15327. color: this.color,
  15328. colorIndex: this.colorIndex,
  15329. key: this.name || this.category,
  15330. series: this.series,
  15331. point: this,
  15332. percentage: this.percentage,
  15333. total: this.total || this.stackTotal
  15334. };
  15335. },
  15336. /**
  15337. * Extendable method for formatting each point's tooltip line
  15338. *
  15339. * @return {String} A string to be concatenated in to the common tooltip text
  15340. */
  15341. tooltipFormatter: function(pointFormat) {
  15342. // Insert options for valueDecimals, valuePrefix, and valueSuffix
  15343. var series = this.series,
  15344. seriesTooltipOptions = series.tooltipOptions,
  15345. valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
  15346. valuePrefix = seriesTooltipOptions.valuePrefix || '',
  15347. valueSuffix = seriesTooltipOptions.valueSuffix || '';
  15348. // Loop over the point array map and replace unformatted values with sprintf formatting markup
  15349. each(series.pointArrayMap || ['y'], function(key) {
  15350. key = '{point.' + key; // without the closing bracket
  15351. if (valuePrefix || valueSuffix) {
  15352. pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);
  15353. }
  15354. pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');
  15355. });
  15356. return format(pointFormat, {
  15357. point: this,
  15358. series: this.series
  15359. });
  15360. },
  15361. /**
  15362. * Fire an event on the Point object.
  15363. * @param {String} eventType
  15364. * @param {Object} eventArgs Additional event arguments
  15365. * @param {Function} defaultFunction Default event handler
  15366. */
  15367. firePointEvent: function(eventType, eventArgs, defaultFunction) {
  15368. var point = this,
  15369. series = this.series,
  15370. seriesOptions = series.options;
  15371. // load event handlers on demand to save time on mouseover/out
  15372. if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
  15373. this.importEvents();
  15374. }
  15375. // add default handler if in selection mode
  15376. if (eventType === 'click' && seriesOptions.allowPointSelect) {
  15377. defaultFunction = function(event) {
  15378. // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
  15379. if (point.select) { // Could be destroyed by prior event handlers (#2911)
  15380. point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
  15381. }
  15382. };
  15383. }
  15384. fireEvent(this, eventType, eventArgs, defaultFunction);
  15385. },
  15386. /**
  15387. * For certain series types, like pie charts, where individual points can
  15388. * be shown or hidden.
  15389. *
  15390. * @name visible
  15391. * @memberOf Highcharts.Point
  15392. * @type {Boolean}
  15393. */
  15394. visible: true
  15395. };
  15396. /**
  15397. * For categorized axes this property holds the category name for the
  15398. * point. For other axes it holds the X value.
  15399. *
  15400. * @name category
  15401. * @memberOf Highcharts.Point
  15402. * @type {String|Number}
  15403. */
  15404. /**
  15405. * The percentage for points in a stacked series or pies.
  15406. *
  15407. * @name percentage
  15408. * @memberOf Highcharts.Point
  15409. * @type {Number}
  15410. */
  15411. /**
  15412. * The total of values in either a stack for stacked series, or a pie in a pie
  15413. * series.
  15414. *
  15415. * @name total
  15416. * @memberOf Highcharts.Point
  15417. * @type {Number}
  15418. */
  15419. /**
  15420. * The x value of the point.
  15421. *
  15422. * @name x
  15423. * @memberOf Highcharts.Point
  15424. * @type {Number}
  15425. */
  15426. /**
  15427. * The y value of the point.
  15428. *
  15429. * @name y
  15430. * @memberOf Highcharts.Point
  15431. * @type {Number}
  15432. */
  15433. }(Highcharts));
  15434. (function(H) {
  15435. /**
  15436. * (c) 2010-2017 Torstein Honsi
  15437. *
  15438. * License: www.highcharts.com/license
  15439. */
  15440. var addEvent = H.addEvent,
  15441. animObject = H.animObject,
  15442. arrayMax = H.arrayMax,
  15443. arrayMin = H.arrayMin,
  15444. correctFloat = H.correctFloat,
  15445. Date = H.Date,
  15446. defaultOptions = H.defaultOptions,
  15447. defaultPlotOptions = H.defaultPlotOptions,
  15448. defined = H.defined,
  15449. each = H.each,
  15450. erase = H.erase,
  15451. extend = H.extend,
  15452. fireEvent = H.fireEvent,
  15453. grep = H.grep,
  15454. isArray = H.isArray,
  15455. isNumber = H.isNumber,
  15456. isString = H.isString,
  15457. LegendSymbolMixin = H.LegendSymbolMixin, // @todo add as a requirement
  15458. merge = H.merge,
  15459. objectEach = H.objectEach,
  15460. pick = H.pick,
  15461. Point = H.Point, // @todo add as a requirement
  15462. removeEvent = H.removeEvent,
  15463. splat = H.splat,
  15464. SVGElement = H.SVGElement,
  15465. syncTimeout = H.syncTimeout,
  15466. win = H.win;
  15467. /**
  15468. * This is the base series prototype that all other series types inherit from.
  15469. * A new series is initiated either through the {@link https://api.highcharts.com/highcharts/series|
  15470. * series} option structure, or after the chart is initiated, through {@link
  15471. * Highcharts.Chart#addSeries}.
  15472. *
  15473. * The object can be accessed in a number of ways. All series and point event
  15474. * handlers give a reference to the `series` object. The chart object has a
  15475. * {@link Highcharts.Chart.series|series} property that is a collection of all
  15476. * the chart's series. The point objects and axis objects also have the same
  15477. * reference.
  15478. *
  15479. * Another way to reference the series programmatically is by `id`. Add an id
  15480. * in the series configuration options, and get the series object by {@link
  15481. * Highcharts.Chart#get}.
  15482. *
  15483. * Configuration options for the series are given in three levels. Options for
  15484. * all series in a chart are given in the {@link https://api.highcharts.com/highcharts/plotOptions.series|
  15485. * plotOptions.series} object. Then options for all series of a specific type
  15486. * are given in the plotOptions of that type, for example `plotOptions.line`.
  15487. * Next, options for one single series are given in the series array, or as
  15488. * arguements to `chart.addSeries`.
  15489. *
  15490. * The data in the series is stored in various arrays.
  15491. *
  15492. * - First, `series.options.data` contains all the original config options for
  15493. * each point whether added by options or methods like `series.addPoint`.
  15494. * - Next, `series.data` contains those values converted to points, but in case
  15495. * the series data length exceeds the `cropThreshold`, or if the data is grouped,
  15496. * `series.data` doesn't contain all the points. It only contains the points that
  15497. * have been created on demand.
  15498. * - Then there's `series.points` that contains all currently visible point
  15499. * objects. In case of cropping, the cropped-away points are not part of this
  15500. * array. The `series.points` array starts at `series.cropStart` compared to
  15501. * `series.data` and `series.options.data`. If however the series data is grouped,
  15502. * these can't be correlated one to one.
  15503. * - `series.xData` and `series.processedXData` contain clean x values, equivalent
  15504. * to `series.data` and `series.points`.
  15505. * - `series.yData` and `series.processedYData` contain clean y values, equivalent
  15506. * to `series.data` and `series.points`.
  15507. *
  15508. * @class Highcharts.Series
  15509. * @param {Highcharts.Chart} chart
  15510. * The chart instance.
  15511. * @param {Object} options
  15512. * The series options.
  15513. */
  15514. H.Series = H.seriesType('line', null, { // base series options
  15515. //cursor: 'default',
  15516. //dashStyle: null,
  15517. //linecap: 'round',
  15518. lineWidth: 2,
  15519. //shadow: false,
  15520. allowPointSelect: false,
  15521. showCheckbox: false,
  15522. animation: {
  15523. duration: 1000
  15524. },
  15525. //clip: true,
  15526. //connectNulls: false,
  15527. //enableMouseTracking: true,
  15528. events: {},
  15529. //legendIndex: 0,
  15530. // stacking: null,
  15531. marker: {
  15532. lineWidth: 0,
  15533. lineColor: '#ffffff',
  15534. //fillColor: null,
  15535. //enabled: true,
  15536. //symbol: null,
  15537. radius: 4,
  15538. states: { // states for a single point
  15539. hover: {
  15540. animation: {
  15541. duration: 50
  15542. },
  15543. enabled: true,
  15544. radiusPlus: 2,
  15545. lineWidthPlus: 1
  15546. },
  15547. select: {
  15548. fillColor: '#cccccc',
  15549. lineColor: '#000000',
  15550. lineWidth: 2
  15551. }
  15552. }
  15553. },
  15554. point: {
  15555. events: {}
  15556. },
  15557. dataLabels: {
  15558. align: 'center',
  15559. // defer: true,
  15560. // enabled: false,
  15561. formatter: function() {
  15562. return this.y === null ? '' : H.numberFormat(this.y, -1);
  15563. },
  15564. style: {
  15565. fontSize: '11px',
  15566. fontWeight: 'bold',
  15567. color: 'contrast',
  15568. textOutline: '1px contrast'
  15569. },
  15570. // backgroundColor: undefined,
  15571. // borderColor: undefined,
  15572. // borderWidth: undefined,
  15573. // shadow: false
  15574. verticalAlign: 'bottom', // above singular point
  15575. x: 0,
  15576. y: 0,
  15577. // borderRadius: undefined,
  15578. padding: 5
  15579. },
  15580. // draw points outside the plot area when the number of points is less than
  15581. // this
  15582. cropThreshold: 300,
  15583. pointRange: 0,
  15584. //pointStart: 0,
  15585. //pointInterval: 1,
  15586. //showInLegend: null, // auto = false for linked series
  15587. softThreshold: true,
  15588. states: { // states for the entire series
  15589. hover: {
  15590. //enabled: false,
  15591. animation: {
  15592. duration: 50
  15593. },
  15594. lineWidthPlus: 1,
  15595. marker: {
  15596. // lineWidth: base + 1,
  15597. // radius: base + 1
  15598. },
  15599. halo: {
  15600. size: 10,
  15601. opacity: 0.25
  15602. }
  15603. },
  15604. select: {
  15605. marker: {}
  15606. }
  15607. },
  15608. stickyTracking: true,
  15609. //tooltip: {
  15610. //pointFormat: '<span style="color:{point.color}">\u25CF</span>' +
  15611. // '{series.name}: <b>{point.y}</b>'
  15612. //valueDecimals: null,
  15613. //xDateFormat: '%A, %b %e, %Y',
  15614. //valuePrefix: '',
  15615. //ySuffix: ''
  15616. //}
  15617. turboThreshold: 1000,
  15618. // zIndex: null
  15619. findNearestPointBy: 'x'
  15620. }, /** @lends Highcharts.Series.prototype */ {
  15621. isCartesian: true,
  15622. pointClass: Point,
  15623. sorted: true, // requires the data to be sorted
  15624. requireSorting: true,
  15625. directTouch: false,
  15626. axisTypes: ['xAxis', 'yAxis'],
  15627. colorCounter: 0,
  15628. // each point's x and y values are stored in this.xData and this.yData
  15629. parallelArrays: ['x', 'y'],
  15630. coll: 'series',
  15631. init: function(chart, options) {
  15632. var series = this,
  15633. events,
  15634. chartSeries = chart.series,
  15635. lastSeries;
  15636. /**
  15637. * Read only. The chart that the series belongs to.
  15638. *
  15639. * @name chart
  15640. * @memberOf Series
  15641. * @type {Chart}
  15642. */
  15643. series.chart = chart;
  15644. /**
  15645. * Read only. The series' type, like "line", "area", "column" etc. The
  15646. * type in the series options anc can be altered using {@link
  15647. * Series#update}.
  15648. *
  15649. * @name type
  15650. * @memberOf Series
  15651. * @type String
  15652. */
  15653. /**
  15654. * Read only. The series' current options. To update, use {@link
  15655. * Series#update}.
  15656. *
  15657. * @name options
  15658. * @memberOf Series
  15659. * @type SeriesOptions
  15660. */
  15661. series.options = options = series.setOptions(options);
  15662. series.linkedSeries = [];
  15663. // bind the axes
  15664. series.bindAxes();
  15665. // set some variables
  15666. extend(series, {
  15667. /**
  15668. * The series name as given in the options. Defaults to
  15669. * "Series {n}".
  15670. *
  15671. * @name name
  15672. * @memberOf Series
  15673. * @type {String}
  15674. */
  15675. name: options.name,
  15676. state: '',
  15677. /**
  15678. * Read only. The series' visibility state as set by {@link
  15679. * Series#show}, {@link Series#hide}, or in the initial
  15680. * configuration.
  15681. *
  15682. * @name visible
  15683. * @memberOf Series
  15684. * @type {Boolean}
  15685. */
  15686. visible: options.visible !== false, // true by default
  15687. /**
  15688. * Read only. The series' selected state as set by {@link
  15689. * Highcharts.Series#select}.
  15690. *
  15691. * @name selected
  15692. * @memberOf Series
  15693. * @type {Boolean}
  15694. */
  15695. selected: options.selected === true // false by default
  15696. });
  15697. // register event listeners
  15698. events = options.events;
  15699. objectEach(events, function(event, eventType) {
  15700. addEvent(series, eventType, event);
  15701. });
  15702. if (
  15703. (events && events.click) ||
  15704. (
  15705. options.point &&
  15706. options.point.events &&
  15707. options.point.events.click
  15708. ) ||
  15709. options.allowPointSelect
  15710. ) {
  15711. chart.runTrackerClick = true;
  15712. }
  15713. series.getColor();
  15714. series.getSymbol();
  15715. // Set the data
  15716. each(series.parallelArrays, function(key) {
  15717. series[key + 'Data'] = [];
  15718. });
  15719. series.setData(options.data, false);
  15720. // Mark cartesian
  15721. if (series.isCartesian) {
  15722. chart.hasCartesianSeries = true;
  15723. }
  15724. // Get the index and register the series in the chart. The index is one
  15725. // more than the current latest series index (#5960).
  15726. if (chartSeries.length) {
  15727. lastSeries = chartSeries[chartSeries.length - 1];
  15728. }
  15729. series._i = pick(lastSeries && lastSeries._i, -1) + 1;
  15730. // Insert the series and re-order all series above the insertion point.
  15731. chart.orderSeries(this.insert(chartSeries));
  15732. },
  15733. /**
  15734. * Insert the series in a collection with other series, either the chart
  15735. * series or yAxis series, in the correct order according to the index
  15736. * option.
  15737. * @param {Array} collection A collection of series.
  15738. * @returns {Number} The index of the series in the collection.
  15739. */
  15740. insert: function(collection) {
  15741. var indexOption = this.options.index,
  15742. i;
  15743. // Insert by index option
  15744. if (isNumber(indexOption)) {
  15745. i = collection.length;
  15746. while (i--) {
  15747. // Loop down until the interted element has higher index
  15748. if (indexOption >=
  15749. pick(collection[i].options.index, collection[i]._i)) {
  15750. collection.splice(i + 1, 0, this);
  15751. break;
  15752. }
  15753. }
  15754. if (i === -1) {
  15755. collection.unshift(this);
  15756. }
  15757. i = i + 1;
  15758. // Or just push it to the end
  15759. } else {
  15760. collection.push(this);
  15761. }
  15762. return pick(i, collection.length - 1);
  15763. },
  15764. /**
  15765. * Set the xAxis and yAxis properties of cartesian series, and register the
  15766. * series in the `axis.series` array.
  15767. *
  15768. * @function #bindAxes
  15769. * @memberOf Series
  15770. * @returns {void}
  15771. */
  15772. bindAxes: function() {
  15773. var series = this,
  15774. seriesOptions = series.options,
  15775. chart = series.chart,
  15776. axisOptions;
  15777. // repeat for xAxis and yAxis
  15778. each(series.axisTypes || [], function(AXIS) {
  15779. // loop through the chart's axis objects
  15780. each(chart[AXIS], function(axis) {
  15781. axisOptions = axis.options;
  15782. // apply if the series xAxis or yAxis option mathches the number
  15783. // of the axis, or if undefined, use the first axis
  15784. if (
  15785. seriesOptions[AXIS] === axisOptions.index ||
  15786. (
  15787. seriesOptions[AXIS] !== undefined &&
  15788. seriesOptions[AXIS] === axisOptions.id
  15789. ) ||
  15790. (
  15791. seriesOptions[AXIS] === undefined &&
  15792. axisOptions.index === 0
  15793. )
  15794. ) {
  15795. // register this series in the axis.series lookup
  15796. series.insert(axis.series);
  15797. // set this series.xAxis or series.yAxis reference
  15798. /**
  15799. * Read only. The unique xAxis object associated with the
  15800. * series.
  15801. *
  15802. * @name xAxis
  15803. * @memberOf Series
  15804. * @type Axis
  15805. */
  15806. /**
  15807. * Read only. The unique yAxis object associated with the
  15808. * series.
  15809. *
  15810. * @name yAxis
  15811. * @memberOf Series
  15812. * @type Axis
  15813. */
  15814. series[AXIS] = axis;
  15815. // mark dirty for redraw
  15816. axis.isDirty = true;
  15817. }
  15818. });
  15819. // The series needs an X and an Y axis
  15820. if (!series[AXIS] && series.optionalAxis !== AXIS) {
  15821. H.error(18, true);
  15822. }
  15823. });
  15824. },
  15825. /**
  15826. * For simple series types like line and column, the data values are held in
  15827. * arrays like xData and yData for quick lookup to find extremes and more.
  15828. * For multidimensional series like bubble and map, this can be extended
  15829. * with arrays like zData and valueData by adding to the
  15830. * series.parallelArrays array.
  15831. */
  15832. updateParallelArrays: function(point, i) {
  15833. var series = point.series,
  15834. args = arguments,
  15835. fn = isNumber(i) ?
  15836. // Insert the value in the given position
  15837. function(key) {
  15838. var val = key === 'y' && series.toYData ?
  15839. series.toYData(point) :
  15840. point[key];
  15841. series[key + 'Data'][i] = val;
  15842. } :
  15843. // Apply the method specified in i with the following arguments
  15844. // as arguments
  15845. function(key) {
  15846. Array.prototype[i].apply(
  15847. series[key + 'Data'],
  15848. Array.prototype.slice.call(args, 2)
  15849. );
  15850. };
  15851. each(series.parallelArrays, fn);
  15852. },
  15853. /**
  15854. * Return an auto incremented x value based on the pointStart and
  15855. * pointInterval options. This is only used if an x value is not given for
  15856. * the point that calls autoIncrement.
  15857. */
  15858. autoIncrement: function() {
  15859. var options = this.options,
  15860. xIncrement = this.xIncrement,
  15861. date,
  15862. pointInterval,
  15863. pointIntervalUnit = options.pointIntervalUnit;
  15864. xIncrement = pick(xIncrement, options.pointStart, 0);
  15865. this.pointInterval = pointInterval = pick(
  15866. this.pointInterval,
  15867. options.pointInterval,
  15868. 1
  15869. );
  15870. // Added code for pointInterval strings
  15871. if (pointIntervalUnit) {
  15872. date = new Date(xIncrement);
  15873. if (pointIntervalUnit === 'day') {
  15874. date = +date[Date.hcSetDate](
  15875. date[Date.hcGetDate]() + pointInterval
  15876. );
  15877. } else if (pointIntervalUnit === 'month') {
  15878. date = +date[Date.hcSetMonth](
  15879. date[Date.hcGetMonth]() + pointInterval
  15880. );
  15881. } else if (pointIntervalUnit === 'year') {
  15882. date = +date[Date.hcSetFullYear](
  15883. date[Date.hcGetFullYear]() + pointInterval
  15884. );
  15885. }
  15886. pointInterval = date - xIncrement;
  15887. }
  15888. this.xIncrement = xIncrement + pointInterval;
  15889. return xIncrement;
  15890. },
  15891. /**
  15892. * Set the series options by merging from the options tree
  15893. * @param {Object} itemOptions
  15894. */
  15895. setOptions: function(itemOptions) {
  15896. var chart = this.chart,
  15897. chartOptions = chart.options,
  15898. plotOptions = chartOptions.plotOptions,
  15899. userOptions = chart.userOptions || {},
  15900. userPlotOptions = userOptions.plotOptions || {},
  15901. typeOptions = plotOptions[this.type],
  15902. options,
  15903. zones;
  15904. this.userOptions = itemOptions;
  15905. // General series options take precedence over type options because
  15906. // otherwise, default type options like column.animation would be
  15907. // overwritten by the general option. But issues have been raised here
  15908. // (#3881), and the solution may be to distinguish between default
  15909. // option and userOptions like in the tooltip below.
  15910. options = merge(
  15911. typeOptions,
  15912. plotOptions.series,
  15913. itemOptions
  15914. );
  15915. // The tooltip options are merged between global and series specific
  15916. // options. Importance order asscendingly:
  15917. // globals: (1)tooltip, (2)plotOptions.series, (3)plotOptions[this.type]
  15918. // init userOptions with possible later updates: 4-6 like 1-3 and
  15919. // (7)this series options
  15920. this.tooltipOptions = merge(
  15921. defaultOptions.tooltip, // 1
  15922. defaultOptions.plotOptions.series &&
  15923. defaultOptions.plotOptions.series.tooltip, // 2
  15924. defaultOptions.plotOptions[this.type].tooltip, // 3
  15925. chartOptions.tooltip.userOptions, // 4
  15926. plotOptions.series && plotOptions.series.tooltip, // 5
  15927. plotOptions[this.type].tooltip, // 6
  15928. itemOptions.tooltip // 7
  15929. );
  15930. // When shared tooltip, stickyTracking is true by default,
  15931. // unless user says otherwise.
  15932. this.stickyTracking = pick(
  15933. itemOptions.stickyTracking,
  15934. userPlotOptions[this.type] &&
  15935. userPlotOptions[this.type].stickyTracking,
  15936. userPlotOptions.series && userPlotOptions.series.stickyTracking,
  15937. (
  15938. this.tooltipOptions.shared && !this.noSharedTooltip ?
  15939. true :
  15940. options.stickyTracking
  15941. )
  15942. );
  15943. // Delete marker object if not allowed (#1125)
  15944. if (typeOptions.marker === null) {
  15945. delete options.marker;
  15946. }
  15947. // Handle color zones
  15948. this.zoneAxis = options.zoneAxis;
  15949. zones = this.zones = (options.zones || []).slice();
  15950. if (
  15951. (options.negativeColor || options.negativeFillColor) &&
  15952. !options.zones
  15953. ) {
  15954. zones.push({
  15955. value: options[this.zoneAxis + 'Threshold'] ||
  15956. options.threshold ||
  15957. 0,
  15958. className: 'highcharts-negative',
  15959. color: options.negativeColor,
  15960. fillColor: options.negativeFillColor
  15961. });
  15962. }
  15963. if (zones.length) { // Push one extra zone for the rest
  15964. if (defined(zones[zones.length - 1].value)) {
  15965. zones.push({
  15966. color: this.color,
  15967. fillColor: this.fillColor
  15968. });
  15969. }
  15970. }
  15971. return options;
  15972. },
  15973. getCyclic: function(prop, value, defaults) {
  15974. var i,
  15975. chart = this.chart,
  15976. userOptions = this.userOptions,
  15977. indexName = prop + 'Index',
  15978. counterName = prop + 'Counter',
  15979. len = defaults ? defaults.length : pick(
  15980. chart.options.chart[prop + 'Count'],
  15981. chart[prop + 'Count']
  15982. ),
  15983. setting;
  15984. if (!value) {
  15985. // Pick up either the colorIndex option, or the _colorIndex after
  15986. // Series.update()
  15987. setting = pick(
  15988. userOptions[indexName],
  15989. userOptions['_' + indexName]
  15990. );
  15991. if (defined(setting)) { // after Series.update()
  15992. i = setting;
  15993. } else {
  15994. // #6138
  15995. if (!chart.series.length) {
  15996. chart[counterName] = 0;
  15997. }
  15998. userOptions['_' + indexName] = i = chart[counterName] % len;
  15999. chart[counterName] += 1;
  16000. }
  16001. if (defaults) {
  16002. value = defaults[i];
  16003. }
  16004. }
  16005. // Set the colorIndex
  16006. if (i !== undefined) {
  16007. this[indexName] = i;
  16008. }
  16009. this[prop] = value;
  16010. },
  16011. /**
  16012. * Get the series' color
  16013. */
  16014. getColor: function() {
  16015. if (this.options.colorByPoint) {
  16016. // #4359, selected slice got series.color even when colorByPoint was
  16017. // set.
  16018. this.options.color = null;
  16019. } else {
  16020. this.getCyclic(
  16021. 'color',
  16022. this.options.color || defaultPlotOptions[this.type].color,
  16023. this.chart.options.colors
  16024. );
  16025. }
  16026. },
  16027. /**
  16028. * Get the series' symbol
  16029. */
  16030. getSymbol: function() {
  16031. var seriesMarkerOption = this.options.marker;
  16032. this.getCyclic(
  16033. 'symbol',
  16034. seriesMarkerOption.symbol,
  16035. this.chart.options.symbols
  16036. );
  16037. },
  16038. drawLegendSymbol: LegendSymbolMixin.drawLineMarker,
  16039. /**
  16040. * Apply a new set of data to the series and optionally redraw it. The new
  16041. * data array is passed by reference (except in case of `updatePoints`), and
  16042. * may later be mutated when updating the chart data.
  16043. *
  16044. * Note the difference in behaviour when setting the same amount of points,
  16045. * or a different amount of points, as handled by the `updatePoints`
  16046. * parameter.
  16047. *
  16048. * @param {SeriesDataOptions} data
  16049. * Takes an array of data in the same format as described under
  16050. * `series<type>data` for the given series type.
  16051. * @param {Boolean} [redraw=true]
  16052. * Whether to redraw the chart after the series is altered. If doing
  16053. * more operations on the chart, it is a good idea to set redraw to
  16054. * false and call {@link Chart#redraw} after.
  16055. * @param {AnimationOptions} [animation]
  16056. * When the updated data is the same length as the existing data,
  16057. * points will be updated by default, and animation visualizes how
  16058. * the points are changed. Set false to disable animation, or a
  16059. * configuration object to set duration or easing.
  16060. * @param {Boolean} [updatePoints=true]
  16061. * When the updated data is the same length as the existing data,
  16062. * points will be updated instead of replaced. This allows updating
  16063. * with animation and performs better. In this case, the original
  16064. * array is not passed by reference. Set false to prevent.
  16065. *
  16066. * @sample highcharts/members/series-setdata/
  16067. * Set new data from a button
  16068. * @sample highcharts/members/series-setdata-pie/
  16069. * Set data in a pie
  16070. * @sample stock/members/series-setdata/
  16071. * Set new data in Highstock
  16072. * @sample maps/members/series-setdata/
  16073. * Set new data in Highmaps
  16074. */
  16075. setData: function(data, redraw, animation, updatePoints) {
  16076. var series = this,
  16077. oldData = series.points,
  16078. oldDataLength = (oldData && oldData.length) || 0,
  16079. dataLength,
  16080. options = series.options,
  16081. chart = series.chart,
  16082. firstPoint = null,
  16083. xAxis = series.xAxis,
  16084. i,
  16085. turboThreshold = options.turboThreshold,
  16086. pt,
  16087. xData = this.xData,
  16088. yData = this.yData,
  16089. pointArrayMap = series.pointArrayMap,
  16090. valueCount = pointArrayMap && pointArrayMap.length;
  16091. data = data || [];
  16092. dataLength = data.length;
  16093. redraw = pick(redraw, true);
  16094. // If the point count is the same as is was, just run Point.update which
  16095. // is cheaper, allows animation, and keeps references to points.
  16096. if (
  16097. updatePoints !== false &&
  16098. dataLength &&
  16099. oldDataLength === dataLength &&
  16100. !series.cropped &&
  16101. !series.hasGroupedData &&
  16102. series.visible
  16103. ) {
  16104. each(data, function(point, i) {
  16105. // .update doesn't exist on a linked, hidden series (#3709)
  16106. if (oldData[i].update && point !== options.data[i]) {
  16107. oldData[i].update(point, false, null, false);
  16108. }
  16109. });
  16110. } else {
  16111. // Reset properties
  16112. series.xIncrement = null;
  16113. series.colorCounter = 0; // for series with colorByPoint (#1547)
  16114. // Update parallel arrays
  16115. each(this.parallelArrays, function(key) {
  16116. series[key + 'Data'].length = 0;
  16117. });
  16118. // In turbo mode, only one- or twodimensional arrays of numbers are
  16119. // allowed. The first value is tested, and we assume that all the
  16120. // rest are defined the same way. Although the 'for' loops are
  16121. // similar, they are repeated inside each if-else conditional for
  16122. // max performance.
  16123. if (turboThreshold && dataLength > turboThreshold) {
  16124. // find the first non-null point
  16125. i = 0;
  16126. while (firstPoint === null && i < dataLength) {
  16127. firstPoint = data[i];
  16128. i++;
  16129. }
  16130. if (isNumber(firstPoint)) { // assume all points are numbers
  16131. for (i = 0; i < dataLength; i++) {
  16132. xData[i] = this.autoIncrement();
  16133. yData[i] = data[i];
  16134. }
  16135. // Assume all points are arrays when first point is
  16136. } else if (isArray(firstPoint)) {
  16137. if (valueCount) { // [x, low, high] or [x, o, h, l, c]
  16138. for (i = 0; i < dataLength; i++) {
  16139. pt = data[i];
  16140. xData[i] = pt[0];
  16141. yData[i] = pt.slice(1, valueCount + 1);
  16142. }
  16143. } else { // [x, y]
  16144. for (i = 0; i < dataLength; i++) {
  16145. pt = data[i];
  16146. xData[i] = pt[0];
  16147. yData[i] = pt[1];
  16148. }
  16149. }
  16150. } else {
  16151. // Highcharts expects configs to be numbers or arrays in
  16152. // turbo mode
  16153. H.error(12);
  16154. }
  16155. } else {
  16156. for (i = 0; i < dataLength; i++) {
  16157. if (data[i] !== undefined) { // stray commas in oldIE
  16158. pt = {
  16159. series: series
  16160. };
  16161. series.pointClass.prototype.applyOptions.apply(
  16162. pt, [data[i]]
  16163. );
  16164. series.updateParallelArrays(pt, i);
  16165. }
  16166. }
  16167. }
  16168. // Forgetting to cast strings to numbers is a common caveat when
  16169. // handling CSV or JSON
  16170. if (isString(yData[0])) {
  16171. H.error(14, true);
  16172. }
  16173. /**
  16174. * Read only. An array containing the series' data point objects. To
  16175. * modify the data, use {@link Highcharts.Series#setData} or {@link
  16176. * Highcharts.Point#update}.
  16177. *
  16178. * @name data
  16179. * @memberOf Highcharts.Series
  16180. * @type {Array.<Highcharts.Point>}
  16181. */
  16182. series.data = [];
  16183. series.options.data = series.userOptions.data = data;
  16184. // destroy old points
  16185. i = oldDataLength;
  16186. while (i--) {
  16187. if (oldData[i] && oldData[i].destroy) {
  16188. oldData[i].destroy();
  16189. }
  16190. }
  16191. // reset minRange (#878)
  16192. if (xAxis) {
  16193. xAxis.minRange = xAxis.userMinRange;
  16194. }
  16195. // redraw
  16196. series.isDirty = chart.isDirtyBox = true;
  16197. series.isDirtyData = !!oldData;
  16198. animation = false;
  16199. }
  16200. // Typically for pie series, points need to be processed and generated
  16201. // prior to rendering the legend
  16202. if (options.legendType === 'point') {
  16203. this.processData();
  16204. this.generatePoints();
  16205. }
  16206. if (redraw) {
  16207. chart.redraw(animation);
  16208. }
  16209. },
  16210. /**
  16211. * Process the data by cropping away unused data points if the series is
  16212. * longer than the crop threshold. This saves computing time for large
  16213. * series.
  16214. */
  16215. processData: function(force) {
  16216. var series = this,
  16217. processedXData = series.xData, // copied during slice operation
  16218. processedYData = series.yData,
  16219. dataLength = processedXData.length,
  16220. croppedData,
  16221. cropStart = 0,
  16222. cropped,
  16223. distance,
  16224. closestPointRange,
  16225. xAxis = series.xAxis,
  16226. i, // loop variable
  16227. options = series.options,
  16228. cropThreshold = options.cropThreshold,
  16229. getExtremesFromAll =
  16230. series.getExtremesFromAll ||
  16231. options.getExtremesFromAll, // #4599
  16232. isCartesian = series.isCartesian,
  16233. xExtremes,
  16234. val2lin = xAxis && xAxis.val2lin,
  16235. isLog = xAxis && xAxis.isLog,
  16236. min,
  16237. max;
  16238. // If the series data or axes haven't changed, don't go through this.
  16239. // Return false to pass the message on to override methods like in data
  16240. // grouping.
  16241. if (
  16242. isCartesian &&
  16243. !series.isDirty &&
  16244. !xAxis.isDirty &&
  16245. !series.yAxis.isDirty &&
  16246. !force
  16247. ) {
  16248. return false;
  16249. }
  16250. if (xAxis) {
  16251. xExtremes = xAxis.getExtremes(); // corrected for log axis (#3053)
  16252. min = xExtremes.min;
  16253. max = xExtremes.max;
  16254. }
  16255. // optionally filter out points outside the plot area
  16256. if (
  16257. isCartesian &&
  16258. series.sorted &&
  16259. !getExtremesFromAll &&
  16260. (!cropThreshold || dataLength > cropThreshold || series.forceCrop)
  16261. ) {
  16262. // it's outside current extremes
  16263. if (
  16264. processedXData[dataLength - 1] < min ||
  16265. processedXData[0] > max
  16266. ) {
  16267. processedXData = [];
  16268. processedYData = [];
  16269. // only crop if it's actually spilling out
  16270. } else if (
  16271. processedXData[0] < min ||
  16272. processedXData[dataLength - 1] > max
  16273. ) {
  16274. croppedData = this.cropData(
  16275. series.xData,
  16276. series.yData,
  16277. min,
  16278. max
  16279. );
  16280. processedXData = croppedData.xData;
  16281. processedYData = croppedData.yData;
  16282. cropStart = croppedData.start;
  16283. cropped = true;
  16284. }
  16285. }
  16286. // Find the closest distance between processed points
  16287. i = processedXData.length || 1;
  16288. while (--i) {
  16289. distance = isLog ?
  16290. val2lin(processedXData[i]) - val2lin(processedXData[i - 1]) :
  16291. processedXData[i] - processedXData[i - 1];
  16292. if (
  16293. distance > 0 &&
  16294. (
  16295. closestPointRange === undefined ||
  16296. distance < closestPointRange
  16297. )
  16298. ) {
  16299. closestPointRange = distance;
  16300. // Unsorted data is not supported by the line tooltip, as well as
  16301. // data grouping and navigation in Stock charts (#725) and width
  16302. // calculation of columns (#1900)
  16303. } else if (distance < 0 && series.requireSorting) {
  16304. H.error(15);
  16305. }
  16306. }
  16307. // Record the properties
  16308. series.cropped = cropped; // undefined or true
  16309. series.cropStart = cropStart;
  16310. series.processedXData = processedXData;
  16311. series.processedYData = processedYData;
  16312. series.closestPointRange = closestPointRange;
  16313. },
  16314. /**
  16315. * Iterate over xData and crop values between min and max. Returns object
  16316. * containing crop start/end cropped xData with corresponding part of yData,
  16317. * dataMin and dataMax within the cropped range
  16318. */
  16319. cropData: function(xData, yData, min, max) {
  16320. var dataLength = xData.length,
  16321. cropStart = 0,
  16322. cropEnd = dataLength,
  16323. // line-type series need one point outside
  16324. cropShoulder = pick(this.cropShoulder, 1),
  16325. i,
  16326. j;
  16327. // iterate up to find slice start
  16328. for (i = 0; i < dataLength; i++) {
  16329. if (xData[i] >= min) {
  16330. cropStart = Math.max(0, i - cropShoulder);
  16331. break;
  16332. }
  16333. }
  16334. // proceed to find slice end
  16335. for (j = i; j < dataLength; j++) {
  16336. if (xData[j] > max) {
  16337. cropEnd = j + cropShoulder;
  16338. break;
  16339. }
  16340. }
  16341. return {
  16342. xData: xData.slice(cropStart, cropEnd),
  16343. yData: yData.slice(cropStart, cropEnd),
  16344. start: cropStart,
  16345. end: cropEnd
  16346. };
  16347. },
  16348. /**
  16349. * Generate the data point after the data has been processed by cropping
  16350. * away unused points and optionally grouped in Highcharts Stock.
  16351. */
  16352. generatePoints: function() {
  16353. var series = this,
  16354. options = series.options,
  16355. dataOptions = options.data,
  16356. data = series.data,
  16357. dataLength,
  16358. processedXData = series.processedXData,
  16359. processedYData = series.processedYData,
  16360. PointClass = series.pointClass,
  16361. processedDataLength = processedXData.length,
  16362. cropStart = series.cropStart || 0,
  16363. cursor,
  16364. hasGroupedData = series.hasGroupedData,
  16365. keys = options.keys,
  16366. point,
  16367. points = [],
  16368. i;
  16369. if (!data && !hasGroupedData) {
  16370. var arr = [];
  16371. arr.length = dataOptions.length;
  16372. data = series.data = arr;
  16373. }
  16374. if (keys && hasGroupedData) {
  16375. // grouped data has already applied keys (#6590)
  16376. series.options.keys = false;
  16377. }
  16378. for (i = 0; i < processedDataLength; i++) {
  16379. cursor = cropStart + i;
  16380. if (!hasGroupedData) {
  16381. point = data[cursor];
  16382. if (!point && dataOptions[cursor] !== undefined) { // #970
  16383. data[cursor] = point = (new PointClass()).init(
  16384. series,
  16385. dataOptions[cursor],
  16386. processedXData[i]
  16387. );
  16388. }
  16389. } else {
  16390. // splat the y data in case of ohlc data array
  16391. point = (new PointClass()).init(
  16392. series, [processedXData[i]].concat(splat(processedYData[i]))
  16393. );
  16394. /**
  16395. * Highstock only. If a point object is created by data
  16396. * grouping, it doesn't reflect actual points in the raw data.
  16397. * In this case, the `dataGroup` property holds information
  16398. * that points back to the raw data.
  16399. *
  16400. * - `dataGroup.start` is the index of the first raw data point
  16401. * in the group.
  16402. * - `dataGroup.length` is the amount of points in the group.
  16403. *
  16404. * @name dataGroup
  16405. * @memberOf Point
  16406. * @type {Object}
  16407. *
  16408. */
  16409. point.dataGroup = series.groupMap[i];
  16410. }
  16411. if (point) { // #6279
  16412. point.index = cursor; // For faster access in Point.update
  16413. points[i] = point;
  16414. }
  16415. }
  16416. // restore keys options (#6590)
  16417. series.options.keys = keys;
  16418. // Hide cropped-away points - this only runs when the number of points
  16419. // is above cropThreshold, or when swithching view from non-grouped
  16420. // data to grouped data (#637)
  16421. if (
  16422. data &&
  16423. (
  16424. processedDataLength !== (dataLength = data.length) ||
  16425. hasGroupedData
  16426. )
  16427. ) {
  16428. for (i = 0; i < dataLength; i++) {
  16429. // when has grouped data, clear all points
  16430. if (i === cropStart && !hasGroupedData) {
  16431. i += processedDataLength;
  16432. }
  16433. if (data[i]) {
  16434. data[i].destroyElements();
  16435. data[i].plotX = undefined; // #1003
  16436. }
  16437. }
  16438. }
  16439. series.data = data;
  16440. series.points = points;
  16441. },
  16442. /**
  16443. * Calculate Y extremes for visible data
  16444. */
  16445. getExtremes: function(yData) {
  16446. var xAxis = this.xAxis,
  16447. yAxis = this.yAxis,
  16448. xData = this.processedXData,
  16449. yDataLength,
  16450. activeYData = [],
  16451. activeCounter = 0,
  16452. // #2117, need to compensate for log X axis
  16453. xExtremes = xAxis.getExtremes(),
  16454. xMin = xExtremes.min,
  16455. xMax = xExtremes.max,
  16456. validValue,
  16457. withinRange,
  16458. x,
  16459. y,
  16460. i,
  16461. j;
  16462. yData = yData || this.stackedYData || this.processedYData || [];
  16463. yDataLength = yData.length;
  16464. for (i = 0; i < yDataLength; i++) {
  16465. x = xData[i];
  16466. y = yData[i];
  16467. // For points within the visible range, including the first point
  16468. // outside the visible range, consider y extremes
  16469. validValue =
  16470. (isNumber(y, true) || isArray(y)) &&
  16471. (!yAxis.positiveValuesOnly || (y.length || y > 0));
  16472. withinRange =
  16473. this.getExtremesFromAll ||
  16474. this.options.getExtremesFromAll ||
  16475. this.cropped ||
  16476. ((xData[i] || x) >= xMin && (xData[i] || x) <= xMax);
  16477. if (validValue && withinRange) {
  16478. j = y.length;
  16479. if (j) { // array, like ohlc or range data
  16480. while (j--) {
  16481. if (y[j] !== null) {
  16482. activeYData[activeCounter++] = y[j];
  16483. }
  16484. }
  16485. } else {
  16486. activeYData[activeCounter++] = y;
  16487. }
  16488. }
  16489. }
  16490. this.dataMin = arrayMin(activeYData);
  16491. this.dataMax = arrayMax(activeYData);
  16492. },
  16493. /**
  16494. * Translate data points from raw data values to chart specific positioning
  16495. * data needed later in drawPoints, drawGraph and drawTracker.
  16496. *
  16497. * @function #translate
  16498. * @memberOf Series
  16499. * @returns {void}
  16500. */
  16501. translate: function() {
  16502. if (!this.processedXData) { // hidden series
  16503. this.processData();
  16504. }
  16505. this.generatePoints();
  16506. var series = this,
  16507. options = series.options,
  16508. stacking = options.stacking,
  16509. xAxis = series.xAxis,
  16510. categories = xAxis.categories,
  16511. yAxis = series.yAxis,
  16512. points = series.points,
  16513. dataLength = points.length,
  16514. hasModifyValue = !!series.modifyValue,
  16515. i,
  16516. pointPlacement = options.pointPlacement,
  16517. dynamicallyPlaced =
  16518. pointPlacement === 'between' ||
  16519. isNumber(pointPlacement),
  16520. threshold = options.threshold,
  16521. stackThreshold = options.startFromThreshold ? threshold : 0,
  16522. plotX,
  16523. plotY,
  16524. lastPlotX,
  16525. stackIndicator,
  16526. closestPointRangePx = Number.MAX_VALUE;
  16527. // Point placement is relative to each series pointRange (#5889)
  16528. if (pointPlacement === 'between') {
  16529. pointPlacement = 0.5;
  16530. }
  16531. if (isNumber(pointPlacement)) {
  16532. pointPlacement *= pick(options.pointRange || xAxis.pointRange);
  16533. }
  16534. // Translate each point
  16535. for (i = 0; i < dataLength; i++) {
  16536. var point = points[i],
  16537. xValue = point.x,
  16538. yValue = point.y,
  16539. yBottom = point.low,
  16540. stack = stacking && yAxis.stacks[(
  16541. series.negStacks &&
  16542. yValue < (stackThreshold ? 0 : threshold) ? '-' : ''
  16543. ) + series.stackKey],
  16544. pointStack,
  16545. stackValues;
  16546. // Discard disallowed y values for log axes (#3434)
  16547. if (yAxis.positiveValuesOnly && yValue !== null && yValue <= 0) {
  16548. point.isNull = true;
  16549. }
  16550. // Get the plotX translation
  16551. point.plotX = plotX = correctFloat( // #5236
  16552. Math.min(Math.max(-1e5, xAxis.translate(
  16553. xValue,
  16554. 0,
  16555. 0,
  16556. 0,
  16557. 1,
  16558. pointPlacement,
  16559. this.type === 'flags'
  16560. )), 1e5) // #3923
  16561. );
  16562. // Calculate the bottom y value for stacked series
  16563. if (
  16564. stacking &&
  16565. series.visible &&
  16566. !point.isNull &&
  16567. stack &&
  16568. stack[xValue]
  16569. ) {
  16570. stackIndicator = series.getStackIndicator(
  16571. stackIndicator,
  16572. xValue,
  16573. series.index
  16574. );
  16575. pointStack = stack[xValue];
  16576. stackValues = pointStack.points[stackIndicator.key];
  16577. yBottom = stackValues[0];
  16578. yValue = stackValues[1];
  16579. if (
  16580. yBottom === stackThreshold &&
  16581. stackIndicator.key === stack[xValue].base
  16582. ) {
  16583. yBottom = pick(threshold, yAxis.min);
  16584. }
  16585. if (yAxis.positiveValuesOnly && yBottom <= 0) { // #1200, #1232
  16586. yBottom = null;
  16587. }
  16588. point.total = point.stackTotal = pointStack.total;
  16589. point.percentage =
  16590. pointStack.total &&
  16591. (point.y / pointStack.total * 100);
  16592. point.stackY = yValue;
  16593. // Place the stack label
  16594. pointStack.setOffset(
  16595. series.pointXOffset || 0,
  16596. series.barW || 0
  16597. );
  16598. }
  16599. // Set translated yBottom or remove it
  16600. point.yBottom = defined(yBottom) ?
  16601. yAxis.translate(yBottom, 0, 1, 0, 1) :
  16602. null;
  16603. // general hook, used for Highstock compare mode
  16604. if (hasModifyValue) {
  16605. yValue = series.modifyValue(yValue, point);
  16606. }
  16607. // Set the the plotY value, reset it for redraws
  16608. point.plotY = plotY =
  16609. (typeof yValue === 'number' && yValue !== Infinity) ?
  16610. Math.min(Math.max(-1e5,
  16611. yAxis.translate(yValue, 0, 1, 0, 1)), 1e5) : // #3201
  16612. undefined;
  16613. point.isInside =
  16614. plotY !== undefined &&
  16615. plotY >= 0 &&
  16616. plotY <= yAxis.len && // #3519
  16617. plotX >= 0 &&
  16618. plotX <= xAxis.len;
  16619. // Set client related positions for mouse tracking
  16620. point.clientX = dynamicallyPlaced ?
  16621. correctFloat(
  16622. xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement)
  16623. ) :
  16624. plotX; // #1514, #5383, #5518
  16625. point.negative = point.y < (threshold || 0);
  16626. // some API data
  16627. point.category = categories && categories[point.x] !== undefined ?
  16628. categories[point.x] : point.x;
  16629. // Determine auto enabling of markers (#3635, #5099)
  16630. if (!point.isNull) {
  16631. if (lastPlotX !== undefined) {
  16632. closestPointRangePx = Math.min(
  16633. closestPointRangePx,
  16634. Math.abs(plotX - lastPlotX)
  16635. );
  16636. }
  16637. lastPlotX = plotX;
  16638. }
  16639. // Find point zone
  16640. point.zone = this.zones.length && point.getZone();
  16641. }
  16642. series.closestPointRangePx = closestPointRangePx;
  16643. },
  16644. /**
  16645. * Return the series points with null points filtered out
  16646. */
  16647. getValidPoints: function(points, insideOnly) {
  16648. var chart = this.chart;
  16649. // #3916, #5029, #5085
  16650. return grep(points || this.points || [], function isValidPoint(point) {
  16651. if (insideOnly && !chart.isInsidePlot(
  16652. point.plotX,
  16653. point.plotY,
  16654. chart.inverted
  16655. )) {
  16656. return false;
  16657. }
  16658. return !point.isNull;
  16659. });
  16660. },
  16661. /**
  16662. * Set the clipping for the series. For animated series it is called twice,
  16663. * first to initiate animating the clip then the second time without the
  16664. * animation to set the final clip.
  16665. */
  16666. setClip: function(animation) {
  16667. var chart = this.chart,
  16668. options = this.options,
  16669. renderer = chart.renderer,
  16670. inverted = chart.inverted,
  16671. seriesClipBox = this.clipBox,
  16672. clipBox = seriesClipBox || chart.clipBox,
  16673. sharedClipKey =
  16674. this.sharedClipKey || [
  16675. '_sharedClip',
  16676. animation && animation.duration,
  16677. animation && animation.easing,
  16678. clipBox.height,
  16679. options.xAxis,
  16680. options.yAxis
  16681. ].join(','), // #4526
  16682. clipRect = chart[sharedClipKey],
  16683. markerClipRect = chart[sharedClipKey + 'm'];
  16684. // If a clipping rectangle with the same properties is currently present
  16685. // in the chart, use that.
  16686. if (!clipRect) {
  16687. // When animation is set, prepare the initial positions
  16688. if (animation) {
  16689. clipBox.width = 0;
  16690. chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(-99, // include the width of the first marker
  16691. inverted ? -chart.plotLeft : -chart.plotTop,
  16692. 99,
  16693. inverted ? chart.chartWidth : chart.chartHeight
  16694. );
  16695. }
  16696. chart[sharedClipKey] = clipRect = renderer.clipRect(clipBox);
  16697. // Create hashmap for series indexes
  16698. clipRect.count = {
  16699. length: 0
  16700. };
  16701. }
  16702. if (animation) {
  16703. if (!clipRect.count[this.index]) {
  16704. clipRect.count[this.index] = true;
  16705. clipRect.count.length += 1;
  16706. }
  16707. }
  16708. if (options.clip !== false) {
  16709. this.group.clip(animation || seriesClipBox ? clipRect : chart.clipRect);
  16710. this.markerGroup.clip(markerClipRect);
  16711. this.sharedClipKey = sharedClipKey;
  16712. }
  16713. // Remove the shared clipping rectangle when all series are shown
  16714. if (!animation) {
  16715. if (clipRect.count[this.index]) {
  16716. delete clipRect.count[this.index];
  16717. clipRect.count.length -= 1;
  16718. }
  16719. if (clipRect.count.length === 0 && sharedClipKey && chart[sharedClipKey]) {
  16720. if (!seriesClipBox) {
  16721. chart[sharedClipKey] = chart[sharedClipKey].destroy();
  16722. }
  16723. if (chart[sharedClipKey + 'm']) {
  16724. chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
  16725. }
  16726. }
  16727. }
  16728. },
  16729. /**
  16730. * Animate in the series
  16731. */
  16732. animate: function(init) {
  16733. var series = this,
  16734. chart = series.chart,
  16735. clipRect,
  16736. animation = animObject(series.options.animation),
  16737. sharedClipKey;
  16738. // Initialize the animation. Set up the clipping rectangle.
  16739. if (init) {
  16740. series.setClip(animation);
  16741. // Run the animation
  16742. } else {
  16743. sharedClipKey = this.sharedClipKey;
  16744. clipRect = chart[sharedClipKey];
  16745. if (clipRect) {
  16746. clipRect.animate({
  16747. width: chart.plotSizeX
  16748. }, animation);
  16749. }
  16750. if (chart[sharedClipKey + 'm']) {
  16751. chart[sharedClipKey + 'm'].animate({
  16752. width: chart.plotSizeX + 99
  16753. }, animation);
  16754. }
  16755. // Delete this function to allow it only once
  16756. series.animate = null;
  16757. }
  16758. },
  16759. /**
  16760. * This runs after animation to land on the final plot clipping
  16761. */
  16762. afterAnimate: function() {
  16763. this.setClip();
  16764. fireEvent(this, 'afterAnimate');
  16765. },
  16766. /**
  16767. * Draw the markers.
  16768. *
  16769. * @function #drawPoints
  16770. * @memberOf Series
  16771. * @returns {void}
  16772. */
  16773. drawPoints: function() {
  16774. var series = this,
  16775. points = series.points,
  16776. chart = series.chart,
  16777. plotY,
  16778. i,
  16779. point,
  16780. symbol,
  16781. graphic,
  16782. options = series.options,
  16783. seriesMarkerOptions = options.marker,
  16784. pointMarkerOptions,
  16785. hasPointMarker,
  16786. enabled,
  16787. isInside,
  16788. markerGroup = series[series.specialGroup] || series.markerGroup,
  16789. xAxis = series.xAxis,
  16790. markerAttribs,
  16791. globallyEnabled = pick(
  16792. seriesMarkerOptions.enabled,
  16793. xAxis.isRadial ? true : null,
  16794. // Use larger or equal as radius is null in bubbles (#6321)
  16795. series.closestPointRangePx >= 2 * seriesMarkerOptions.radius
  16796. );
  16797. if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) {
  16798. for (i = 0; i < points.length; i++) {
  16799. point = points[i];
  16800. plotY = point.plotY;
  16801. graphic = point.graphic;
  16802. pointMarkerOptions = point.marker || {};
  16803. hasPointMarker = !!point.marker;
  16804. enabled = (globallyEnabled && pointMarkerOptions.enabled === undefined) || pointMarkerOptions.enabled;
  16805. isInside = point.isInside;
  16806. // only draw the point if y is defined
  16807. if (enabled && isNumber(plotY) && point.y !== null) {
  16808. // Shortcuts
  16809. symbol = pick(pointMarkerOptions.symbol, series.symbol);
  16810. point.hasImage = symbol.indexOf('url') === 0;
  16811. markerAttribs = series.markerAttribs(
  16812. point,
  16813. point.selected && 'select'
  16814. );
  16815. if (graphic) { // update
  16816. graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled
  16817. .animate(markerAttribs);
  16818. } else if (isInside && (markerAttribs.width > 0 || point.hasImage)) {
  16819. point.graphic = graphic = chart.renderer.symbol(
  16820. symbol,
  16821. markerAttribs.x,
  16822. markerAttribs.y,
  16823. markerAttribs.width,
  16824. markerAttribs.height,
  16825. hasPointMarker ? pointMarkerOptions : seriesMarkerOptions
  16826. )
  16827. .add(markerGroup);
  16828. }
  16829. // Presentational attributes
  16830. if (graphic) {
  16831. graphic.attr(series.pointAttribs(point, point.selected && 'select'));
  16832. }
  16833. if (graphic) {
  16834. graphic.addClass(point.getClassName(), true);
  16835. }
  16836. } else if (graphic) {
  16837. point.graphic = graphic.destroy(); // #1269
  16838. }
  16839. }
  16840. }
  16841. },
  16842. /**
  16843. * Get non-presentational attributes for the point.
  16844. */
  16845. markerAttribs: function(point, state) {
  16846. var seriesMarkerOptions = this.options.marker,
  16847. seriesStateOptions,
  16848. pointMarkerOptions = point.marker || {},
  16849. pointStateOptions,
  16850. radius = pick(
  16851. pointMarkerOptions.radius,
  16852. seriesMarkerOptions.radius
  16853. ),
  16854. attribs;
  16855. // Handle hover and select states
  16856. if (state) {
  16857. seriesStateOptions = seriesMarkerOptions.states[state];
  16858. pointStateOptions = pointMarkerOptions.states &&
  16859. pointMarkerOptions.states[state];
  16860. radius = pick(
  16861. pointStateOptions && pointStateOptions.radius,
  16862. seriesStateOptions && seriesStateOptions.radius,
  16863. radius + (seriesStateOptions && seriesStateOptions.radiusPlus || 0)
  16864. );
  16865. }
  16866. if (point.hasImage) {
  16867. radius = 0; // and subsequently width and height is not set
  16868. }
  16869. attribs = {
  16870. x: Math.floor(point.plotX) - radius, // Math.floor for #1843
  16871. y: point.plotY - radius
  16872. };
  16873. if (radius) {
  16874. attribs.width = attribs.height = 2 * radius;
  16875. }
  16876. return attribs;
  16877. },
  16878. /**
  16879. * Get presentational attributes for marker-based series (line, spline, scatter, bubble, mappoint...)
  16880. */
  16881. pointAttribs: function(point, state) {
  16882. var seriesMarkerOptions = this.options.marker,
  16883. seriesStateOptions,
  16884. pointOptions = point && point.options,
  16885. pointMarkerOptions = (pointOptions && pointOptions.marker) || {},
  16886. pointStateOptions,
  16887. color = this.color,
  16888. pointColorOption = pointOptions && pointOptions.color,
  16889. pointColor = point && point.color,
  16890. strokeWidth = pick(
  16891. pointMarkerOptions.lineWidth,
  16892. seriesMarkerOptions.lineWidth
  16893. ),
  16894. zoneColor = point && point.zone && point.zone.color,
  16895. fill,
  16896. stroke;
  16897. color = pointColorOption || zoneColor || pointColor || color;
  16898. fill = pointMarkerOptions.fillColor || seriesMarkerOptions.fillColor || color;
  16899. stroke = pointMarkerOptions.lineColor || seriesMarkerOptions.lineColor || color;
  16900. // Handle hover and select states
  16901. if (state) {
  16902. seriesStateOptions = seriesMarkerOptions.states[state];
  16903. pointStateOptions = (pointMarkerOptions.states && pointMarkerOptions.states[state]) || {};
  16904. strokeWidth = pick(
  16905. pointStateOptions.lineWidth,
  16906. seriesStateOptions.lineWidth,
  16907. strokeWidth + pick(
  16908. pointStateOptions.lineWidthPlus,
  16909. seriesStateOptions.lineWidthPlus,
  16910. 0
  16911. )
  16912. );
  16913. fill = pointStateOptions.fillColor || seriesStateOptions.fillColor || fill;
  16914. stroke = pointStateOptions.lineColor || seriesStateOptions.lineColor || stroke;
  16915. }
  16916. return {
  16917. 'stroke': stroke,
  16918. 'stroke-width': strokeWidth,
  16919. 'fill': fill
  16920. };
  16921. },
  16922. /**
  16923. * Clear DOM objects and free up memory
  16924. */
  16925. destroy: function() {
  16926. var series = this,
  16927. chart = series.chart,
  16928. issue134 = /AppleWebKit\/533/.test(win.navigator.userAgent),
  16929. destroy,
  16930. i,
  16931. data = series.data || [],
  16932. point,
  16933. axis;
  16934. // add event hook
  16935. fireEvent(series, 'destroy');
  16936. // remove all events
  16937. removeEvent(series);
  16938. // erase from axes
  16939. each(series.axisTypes || [], function(AXIS) {
  16940. axis = series[AXIS];
  16941. if (axis && axis.series) {
  16942. erase(axis.series, series);
  16943. axis.isDirty = axis.forceRedraw = true;
  16944. }
  16945. });
  16946. // remove legend items
  16947. if (series.legendItem) {
  16948. series.chart.legend.destroyItem(series);
  16949. }
  16950. // destroy all points with their elements
  16951. i = data.length;
  16952. while (i--) {
  16953. point = data[i];
  16954. if (point && point.destroy) {
  16955. point.destroy();
  16956. }
  16957. }
  16958. series.points = null;
  16959. // Clear the animation timeout if we are destroying the series during initial animation
  16960. clearTimeout(series.animationTimeout);
  16961. // Destroy all SVGElements associated to the series
  16962. objectEach(series, function(val, prop) {
  16963. if (val instanceof SVGElement && !val.survive) { // Survive provides a hook for not destroying
  16964. // issue 134 workaround
  16965. destroy = issue134 && prop === 'group' ?
  16966. 'hide' :
  16967. 'destroy';
  16968. val[destroy]();
  16969. }
  16970. });
  16971. // remove from hoverSeries
  16972. if (chart.hoverSeries === series) {
  16973. chart.hoverSeries = null;
  16974. }
  16975. erase(chart.series, series);
  16976. chart.orderSeries();
  16977. // clear all members
  16978. objectEach(series, function(val, prop) {
  16979. delete series[prop];
  16980. });
  16981. },
  16982. /**
  16983. * Get the graph path
  16984. */
  16985. getGraphPath: function(points, nullsAsZeroes, connectCliffs) {
  16986. var series = this,
  16987. options = series.options,
  16988. step = options.step,
  16989. reversed,
  16990. graphPath = [],
  16991. xMap = [],
  16992. gap;
  16993. points = points || series.points;
  16994. // Bottom of a stack is reversed
  16995. reversed = points.reversed;
  16996. if (reversed) {
  16997. points.reverse();
  16998. }
  16999. // Reverse the steps (#5004)
  17000. step = {
  17001. right: 1,
  17002. center: 2
  17003. }[step] || (step && 3);
  17004. if (step && reversed) {
  17005. step = 4 - step;
  17006. }
  17007. // Remove invalid points, especially in spline (#5015)
  17008. if (options.connectNulls && !nullsAsZeroes && !connectCliffs) {
  17009. points = this.getValidPoints(points);
  17010. }
  17011. // Build the line
  17012. each(points, function(point, i) {
  17013. var plotX = point.plotX,
  17014. plotY = point.plotY,
  17015. lastPoint = points[i - 1],
  17016. pathToPoint; // the path to this point from the previous
  17017. if ((point.leftCliff || (lastPoint && lastPoint.rightCliff)) && !connectCliffs) {
  17018. gap = true; // ... and continue
  17019. }
  17020. // Line series, nullsAsZeroes is not handled
  17021. if (point.isNull && !defined(nullsAsZeroes) && i > 0) {
  17022. gap = !options.connectNulls;
  17023. // Area series, nullsAsZeroes is set
  17024. } else if (point.isNull && !nullsAsZeroes) {
  17025. gap = true;
  17026. } else {
  17027. if (i === 0 || gap) {
  17028. pathToPoint = ['M', point.plotX, point.plotY];
  17029. } else if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
  17030. pathToPoint = series.getPointSpline(points, point, i);
  17031. } else if (step) {
  17032. if (step === 1) { // right
  17033. pathToPoint = [
  17034. 'L',
  17035. lastPoint.plotX,
  17036. plotY
  17037. ];
  17038. } else if (step === 2) { // center
  17039. pathToPoint = [
  17040. 'L',
  17041. (lastPoint.plotX + plotX) / 2,
  17042. lastPoint.plotY,
  17043. 'L',
  17044. (lastPoint.plotX + plotX) / 2,
  17045. plotY
  17046. ];
  17047. } else {
  17048. pathToPoint = [
  17049. 'L',
  17050. plotX,
  17051. lastPoint.plotY
  17052. ];
  17053. }
  17054. pathToPoint.push('L', plotX, plotY);
  17055. } else {
  17056. // normal line to next point
  17057. pathToPoint = [
  17058. 'L',
  17059. plotX,
  17060. plotY
  17061. ];
  17062. }
  17063. // Prepare for animation. When step is enabled, there are two path nodes for each x value.
  17064. xMap.push(point.x);
  17065. if (step) {
  17066. xMap.push(point.x);
  17067. }
  17068. graphPath.push.apply(graphPath, pathToPoint);
  17069. gap = false;
  17070. }
  17071. });
  17072. graphPath.xMap = xMap;
  17073. series.graphPath = graphPath;
  17074. return graphPath;
  17075. },
  17076. /**
  17077. * Draw the actual graph
  17078. */
  17079. drawGraph: function() {
  17080. var series = this,
  17081. options = this.options,
  17082. graphPath = (this.gappedPath || this.getGraphPath).call(this),
  17083. props = [
  17084. [
  17085. 'graph',
  17086. 'highcharts-graph',
  17087. options.lineColor || this.color,
  17088. options.dashStyle
  17089. ]
  17090. ];
  17091. // Add the zone properties if any
  17092. each(this.zones, function(zone, i) {
  17093. props.push([
  17094. 'zone-graph-' + i,
  17095. 'highcharts-graph highcharts-zone-graph-' + i + ' ' + (zone.className || ''),
  17096. zone.color || series.color,
  17097. zone.dashStyle || options.dashStyle
  17098. ]);
  17099. });
  17100. // Draw the graph
  17101. each(props, function(prop, i) {
  17102. var graphKey = prop[0],
  17103. graph = series[graphKey],
  17104. attribs;
  17105. if (graph) {
  17106. graph.endX = graphPath.xMap;
  17107. graph.animate({
  17108. d: graphPath
  17109. });
  17110. } else if (graphPath.length) { // #1487
  17111. series[graphKey] = series.chart.renderer.path(graphPath)
  17112. .addClass(prop[1])
  17113. .attr({
  17114. zIndex: 1
  17115. }) // #1069
  17116. .add(series.group);
  17117. attribs = {
  17118. 'stroke': prop[2],
  17119. 'stroke-width': options.lineWidth,
  17120. 'fill': (series.fillGraph && series.color) || 'none' // Polygon series use filled graph
  17121. };
  17122. if (prop[3]) {
  17123. attribs.dashstyle = prop[3];
  17124. } else if (options.linecap !== 'square') {
  17125. attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round';
  17126. }
  17127. graph = series[graphKey]
  17128. .attr(attribs)
  17129. .shadow((i < 2) && options.shadow); // add shadow to normal series (0) or to first zone (1) #3932
  17130. }
  17131. // Helpers for animation
  17132. if (graph) {
  17133. graph.startX = graphPath.xMap;
  17134. //graph.shiftUnit = options.step ? 2 : 1;
  17135. graph.isArea = graphPath.isArea; // For arearange animation
  17136. }
  17137. });
  17138. },
  17139. /**
  17140. * Clip the graphs into the positive and negative coloured graphs
  17141. */
  17142. applyZones: function() {
  17143. var series = this,
  17144. chart = this.chart,
  17145. renderer = chart.renderer,
  17146. zones = this.zones,
  17147. translatedFrom,
  17148. translatedTo,
  17149. clips = this.clips || [],
  17150. clipAttr,
  17151. graph = this.graph,
  17152. area = this.area,
  17153. chartSizeMax = Math.max(chart.chartWidth, chart.chartHeight),
  17154. axis = this[(this.zoneAxis || 'y') + 'Axis'],
  17155. extremes,
  17156. reversed,
  17157. inverted = chart.inverted,
  17158. horiz,
  17159. pxRange,
  17160. pxPosMin,
  17161. pxPosMax,
  17162. ignoreZones = false;
  17163. if (zones.length && (graph || area) && axis && axis.min !== undefined) {
  17164. reversed = axis.reversed;
  17165. horiz = axis.horiz;
  17166. // The use of the Color Threshold assumes there are no gaps
  17167. // so it is safe to hide the original graph and area
  17168. if (graph) {
  17169. graph.hide();
  17170. }
  17171. if (area) {
  17172. area.hide();
  17173. }
  17174. // Create the clips
  17175. extremes = axis.getExtremes();
  17176. each(zones, function(threshold, i) {
  17177. translatedFrom = reversed ?
  17178. (horiz ? chart.plotWidth : 0) :
  17179. (horiz ? 0 : axis.toPixels(extremes.min));
  17180. translatedFrom = Math.min(Math.max(pick(translatedTo, translatedFrom), 0), chartSizeMax);
  17181. translatedTo = Math.min(Math.max(Math.round(axis.toPixels(pick(threshold.value, extremes.max), true)), 0), chartSizeMax);
  17182. if (ignoreZones) {
  17183. translatedFrom = translatedTo = axis.toPixels(extremes.max);
  17184. }
  17185. pxRange = Math.abs(translatedFrom - translatedTo);
  17186. pxPosMin = Math.min(translatedFrom, translatedTo);
  17187. pxPosMax = Math.max(translatedFrom, translatedTo);
  17188. if (axis.isXAxis) {
  17189. clipAttr = {
  17190. x: inverted ? pxPosMax : pxPosMin,
  17191. y: 0,
  17192. width: pxRange,
  17193. height: chartSizeMax
  17194. };
  17195. if (!horiz) {
  17196. clipAttr.x = chart.plotHeight - clipAttr.x;
  17197. }
  17198. } else {
  17199. clipAttr = {
  17200. x: 0,
  17201. y: inverted ? pxPosMax : pxPosMin,
  17202. width: chartSizeMax,
  17203. height: pxRange
  17204. };
  17205. if (horiz) {
  17206. clipAttr.y = chart.plotWidth - clipAttr.y;
  17207. }
  17208. }
  17209. /// VML SUPPPORT
  17210. if (inverted && renderer.isVML) {
  17211. if (axis.isXAxis) {
  17212. clipAttr = {
  17213. x: 0,
  17214. y: reversed ? pxPosMin : pxPosMax,
  17215. height: clipAttr.width,
  17216. width: chart.chartWidth
  17217. };
  17218. } else {
  17219. clipAttr = {
  17220. x: clipAttr.y - chart.plotLeft - chart.spacingBox.x,
  17221. y: 0,
  17222. width: clipAttr.height,
  17223. height: chart.chartHeight
  17224. };
  17225. }
  17226. }
  17227. /// END OF VML SUPPORT
  17228. if (clips[i]) {
  17229. clips[i].animate(clipAttr);
  17230. } else {
  17231. clips[i] = renderer.clipRect(clipAttr);
  17232. if (graph) {
  17233. series['zone-graph-' + i].clip(clips[i]);
  17234. }
  17235. if (area) {
  17236. series['zone-area-' + i].clip(clips[i]);
  17237. }
  17238. }
  17239. // if this zone extends out of the axis, ignore the others
  17240. ignoreZones = threshold.value > extremes.max;
  17241. });
  17242. this.clips = clips;
  17243. }
  17244. },
  17245. /**
  17246. * Initialize and perform group inversion on series.group and series.markerGroup
  17247. */
  17248. invertGroups: function(inverted) {
  17249. var series = this,
  17250. chart = series.chart,
  17251. remover;
  17252. function setInvert() {
  17253. each(['group', 'markerGroup'], function(groupName) {
  17254. if (series[groupName]) {
  17255. // VML/HTML needs explicit attributes for flipping
  17256. if (chart.renderer.isVML) {
  17257. series[groupName].attr({
  17258. width: series.yAxis.len,
  17259. height: series.xAxis.len
  17260. });
  17261. }
  17262. series[groupName].width = series.yAxis.len;
  17263. series[groupName].height = series.xAxis.len;
  17264. series[groupName].invert(inverted);
  17265. }
  17266. });
  17267. }
  17268. // Pie, go away (#1736)
  17269. if (!series.xAxis) {
  17270. return;
  17271. }
  17272. // A fixed size is needed for inversion to work
  17273. remover = addEvent(chart, 'resize', setInvert);
  17274. addEvent(series, 'destroy', remover);
  17275. // Do it now
  17276. setInvert(inverted); // do it now
  17277. // On subsequent render and redraw, just do setInvert without setting up events again
  17278. series.invertGroups = setInvert;
  17279. },
  17280. /**
  17281. * General abstraction for creating plot groups like series.group,
  17282. * series.dataLabelsGroup and series.markerGroup. On subsequent calls, the
  17283. * group will only be adjusted to the updated plot size.
  17284. */
  17285. plotGroup: function(prop, name, visibility, zIndex, parent) {
  17286. var group = this[prop],
  17287. isNew = !group;
  17288. // Generate it on first call
  17289. if (isNew) {
  17290. this[prop] = group = this.chart.renderer.g()
  17291. .attr({
  17292. zIndex: zIndex || 0.1 // IE8 and pointer logic use this
  17293. })
  17294. .add(parent);
  17295. }
  17296. // Add the class names, and replace existing ones as response to
  17297. // Series.update (#6660)
  17298. group.addClass(
  17299. (
  17300. 'highcharts-' + name +
  17301. ' highcharts-series-' + this.index +
  17302. ' highcharts-' + this.type + '-series ' +
  17303. 'highcharts-color-' + this.colorIndex + ' ' +
  17304. (this.options.className || '')
  17305. ),
  17306. true
  17307. );
  17308. // Place it on first and subsequent (redraw) calls
  17309. group.attr({
  17310. visibility: visibility
  17311. })[isNew ? 'attr' : 'animate'](
  17312. this.getPlotBox()
  17313. );
  17314. return group;
  17315. },
  17316. /**
  17317. * Get the translation and scale for the plot area of this series
  17318. */
  17319. getPlotBox: function() {
  17320. var chart = this.chart,
  17321. xAxis = this.xAxis,
  17322. yAxis = this.yAxis;
  17323. // Swap axes for inverted (#2339)
  17324. if (chart.inverted) {
  17325. xAxis = yAxis;
  17326. yAxis = this.xAxis;
  17327. }
  17328. return {
  17329. translateX: xAxis ? xAxis.left : chart.plotLeft,
  17330. translateY: yAxis ? yAxis.top : chart.plotTop,
  17331. scaleX: 1, // #1623
  17332. scaleY: 1
  17333. };
  17334. },
  17335. /**
  17336. * Render the graph and markers
  17337. */
  17338. render: function() {
  17339. var series = this,
  17340. chart = series.chart,
  17341. group,
  17342. options = series.options,
  17343. // Animation doesn't work in IE8 quirks when the group div is
  17344. // hidden, and looks bad in other oldIE
  17345. animDuration = (!!series.animate &&
  17346. chart.renderer.isSVG &&
  17347. animObject(options.animation).duration
  17348. ),
  17349. visibility = series.visible ? 'inherit' : 'hidden', // #2597
  17350. zIndex = options.zIndex,
  17351. hasRendered = series.hasRendered,
  17352. chartSeriesGroup = chart.seriesGroup,
  17353. inverted = chart.inverted;
  17354. // the group
  17355. group = series.plotGroup(
  17356. 'group',
  17357. 'series',
  17358. visibility,
  17359. zIndex,
  17360. chartSeriesGroup
  17361. );
  17362. series.markerGroup = series.plotGroup(
  17363. 'markerGroup',
  17364. 'markers',
  17365. visibility,
  17366. zIndex,
  17367. chartSeriesGroup
  17368. );
  17369. // initiate the animation
  17370. if (animDuration) {
  17371. series.animate(true);
  17372. }
  17373. // SVGRenderer needs to know this before drawing elements (#1089, #1795)
  17374. group.inverted = series.isCartesian ? inverted : false;
  17375. // draw the graph if any
  17376. if (series.drawGraph) {
  17377. series.drawGraph();
  17378. series.applyZones();
  17379. }
  17380. /* each(series.points, function (point) {
  17381. if (point.redraw) {
  17382. point.redraw();
  17383. }
  17384. });*/
  17385. // draw the data labels (inn pies they go before the points)
  17386. if (series.drawDataLabels) {
  17387. series.drawDataLabels();
  17388. }
  17389. // draw the points
  17390. if (series.visible) {
  17391. series.drawPoints();
  17392. }
  17393. // draw the mouse tracking area
  17394. if (
  17395. series.drawTracker &&
  17396. series.options.enableMouseTracking !== false
  17397. ) {
  17398. series.drawTracker();
  17399. }
  17400. // Handle inverted series and tracker groups
  17401. series.invertGroups(inverted);
  17402. // Initial clipping, must be defined after inverting groups for VML.
  17403. // Applies to columns etc. (#3839).
  17404. if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
  17405. group.clip(chart.clipRect);
  17406. }
  17407. // Run the animation
  17408. if (animDuration) {
  17409. series.animate();
  17410. }
  17411. // Call the afterAnimate function on animation complete (but don't
  17412. // overwrite the animation.complete option which should be available to
  17413. // the user).
  17414. if (!hasRendered) {
  17415. series.animationTimeout = syncTimeout(function() {
  17416. series.afterAnimate();
  17417. }, animDuration);
  17418. }
  17419. series.isDirty = false; // means data is in accordance with what you see
  17420. // (See #322) series.isDirty = series.isDirtyData = false; // means
  17421. // data is in accordance with what you see
  17422. series.hasRendered = true;
  17423. },
  17424. /**
  17425. * Redraw the series after an update in the axes.
  17426. */
  17427. redraw: function() {
  17428. var series = this,
  17429. chart = series.chart,
  17430. // cache it here as it is set to false in render, but used after
  17431. wasDirty = series.isDirty || series.isDirtyData,
  17432. group = series.group,
  17433. xAxis = series.xAxis,
  17434. yAxis = series.yAxis;
  17435. // reposition on resize
  17436. if (group) {
  17437. if (chart.inverted) {
  17438. group.attr({
  17439. width: chart.plotWidth,
  17440. height: chart.plotHeight
  17441. });
  17442. }
  17443. group.animate({
  17444. translateX: pick(xAxis && xAxis.left, chart.plotLeft),
  17445. translateY: pick(yAxis && yAxis.top, chart.plotTop)
  17446. });
  17447. }
  17448. series.translate();
  17449. series.render();
  17450. if (wasDirty) { // #3868, #3945
  17451. delete this.kdTree;
  17452. }
  17453. },
  17454. /**
  17455. * KD Tree && PointSearching Implementation
  17456. */
  17457. kdAxisArray: ['clientX', 'plotY'],
  17458. searchPoint: function(e, compareX) {
  17459. var series = this,
  17460. xAxis = series.xAxis,
  17461. yAxis = series.yAxis,
  17462. inverted = series.chart.inverted;
  17463. return this.searchKDTree({
  17464. clientX: inverted ?
  17465. xAxis.len - e.chartY + xAxis.pos : e.chartX - xAxis.pos,
  17466. plotY: inverted ?
  17467. yAxis.len - e.chartX + yAxis.pos : e.chartY - yAxis.pos
  17468. }, compareX);
  17469. },
  17470. /**
  17471. * Build the k-d-tree that is used by mouse and touch interaction to get the
  17472. * closest point. Line-like series typically have a one-dimensional tree
  17473. * where points are searched along the X axis, while scatter-like series
  17474. * typically search in two dimensions, X and Y.
  17475. */
  17476. buildKDTree: function() {
  17477. // Prevent multiple k-d-trees from being built simultaneously (#6235)
  17478. this.buildingKdTree = true;
  17479. var series = this,
  17480. dimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
  17481. 2 : 1;
  17482. // Internal function
  17483. function _kdtree(points, depth, dimensions) {
  17484. var axis,
  17485. median,
  17486. length = points && points.length;
  17487. if (length) {
  17488. // alternate between the axis
  17489. axis = series.kdAxisArray[depth % dimensions];
  17490. // sort point array
  17491. points.sort(function(a, b) {
  17492. return a[axis] - b[axis];
  17493. });
  17494. median = Math.floor(length / 2);
  17495. // build and return nod
  17496. return {
  17497. point: points[median],
  17498. left: _kdtree(
  17499. points.slice(0, median), depth + 1, dimensions
  17500. ),
  17501. right: _kdtree(
  17502. points.slice(median + 1), depth + 1, dimensions
  17503. )
  17504. };
  17505. }
  17506. }
  17507. // Start the recursive build process with a clone of the points array
  17508. // and null points filtered out (#3873)
  17509. function startRecursive() {
  17510. series.kdTree = _kdtree(
  17511. series.getValidPoints(
  17512. null,
  17513. // For line-type series restrict to plot area, but
  17514. // column-type series not (#3916, #4511)
  17515. !series.directTouch
  17516. ),
  17517. dimensions,
  17518. dimensions
  17519. );
  17520. series.buildingKdTree = false;
  17521. }
  17522. delete series.kdTree;
  17523. // For testing tooltips, don't build async
  17524. syncTimeout(startRecursive, series.options.kdNow ? 0 : 1);
  17525. },
  17526. searchKDTree: function(point, compareX) {
  17527. var series = this,
  17528. kdX = this.kdAxisArray[0],
  17529. kdY = this.kdAxisArray[1],
  17530. kdComparer = compareX ? 'distX' : 'dist',
  17531. kdDimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
  17532. 2 : 1;
  17533. // Set the one and two dimensional distance on the point object
  17534. function setDistance(p1, p2) {
  17535. var x = (defined(p1[kdX]) && defined(p2[kdX])) ?
  17536. Math.pow(p1[kdX] - p2[kdX], 2) :
  17537. null,
  17538. y = (defined(p1[kdY]) && defined(p2[kdY])) ?
  17539. Math.pow(p1[kdY] - p2[kdY], 2) :
  17540. null,
  17541. r = (x || 0) + (y || 0);
  17542. p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE;
  17543. p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE;
  17544. }
  17545. function _search(search, tree, depth, dimensions) {
  17546. var point = tree.point,
  17547. axis = series.kdAxisArray[depth % dimensions],
  17548. tdist,
  17549. sideA,
  17550. sideB,
  17551. ret = point,
  17552. nPoint1,
  17553. nPoint2;
  17554. setDistance(search, point);
  17555. // Pick side based on distance to splitting point
  17556. tdist = search[axis] - point[axis];
  17557. sideA = tdist < 0 ? 'left' : 'right';
  17558. sideB = tdist < 0 ? 'right' : 'left';
  17559. // End of tree
  17560. if (tree[sideA]) {
  17561. nPoint1 = _search(search, tree[sideA], depth + 1, dimensions);
  17562. ret = (nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point);
  17563. }
  17564. if (tree[sideB]) {
  17565. // compare distance to current best to splitting point to decide
  17566. // wether to check side B or not
  17567. if (Math.sqrt(tdist * tdist) < ret[kdComparer]) {
  17568. nPoint2 = _search(
  17569. search,
  17570. tree[sideB],
  17571. depth + 1,
  17572. dimensions
  17573. );
  17574. ret = nPoint2[kdComparer] < ret[kdComparer] ?
  17575. nPoint2 :
  17576. ret;
  17577. }
  17578. }
  17579. return ret;
  17580. }
  17581. if (!this.kdTree && !this.buildingKdTree) {
  17582. this.buildKDTree();
  17583. }
  17584. if (this.kdTree) {
  17585. return _search(point, this.kdTree, kdDimensions, kdDimensions);
  17586. }
  17587. }
  17588. }); // end Series prototype
  17589. }(Highcharts));
  17590. (function(H) {
  17591. /**
  17592. * (c) 2010-2017 Torstein Honsi
  17593. *
  17594. * License: www.highcharts.com/license
  17595. */
  17596. var Axis = H.Axis,
  17597. Chart = H.Chart,
  17598. correctFloat = H.correctFloat,
  17599. defined = H.defined,
  17600. destroyObjectProperties = H.destroyObjectProperties,
  17601. each = H.each,
  17602. format = H.format,
  17603. objectEach = H.objectEach,
  17604. pick = H.pick,
  17605. Series = H.Series;
  17606. /**
  17607. * The class for stacks. Each stack, on a specific X value and either negative
  17608. * or positive, has its own stack item.
  17609. *
  17610. * @class
  17611. */
  17612. function StackItem(axis, options, isNegative, x, stackOption) {
  17613. var inverted = axis.chart.inverted;
  17614. this.axis = axis;
  17615. // Tells if the stack is negative
  17616. this.isNegative = isNegative;
  17617. // Save the options to be able to style the label
  17618. this.options = options;
  17619. // Save the x value to be able to position the label later
  17620. this.x = x;
  17621. // Initialize total value
  17622. this.total = null;
  17623. // This will keep each points' extremes stored by series.index and point
  17624. // index
  17625. this.points = {};
  17626. // Save the stack option on the series configuration object, and whether to
  17627. // treat it as percent
  17628. this.stack = stackOption;
  17629. this.leftCliff = 0;
  17630. this.rightCliff = 0;
  17631. // The align options and text align varies on whether the stack is negative
  17632. // and if the chart is inverted or not.
  17633. // First test the user supplied value, then use the dynamic.
  17634. this.alignOptions = {
  17635. align: options.align ||
  17636. (inverted ? (isNegative ? 'left' : 'right') : 'center'),
  17637. verticalAlign: options.verticalAlign ||
  17638. (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
  17639. y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
  17640. x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
  17641. };
  17642. this.textAlign = options.textAlign ||
  17643. (inverted ? (isNegative ? 'right' : 'left') : 'center');
  17644. }
  17645. StackItem.prototype = {
  17646. destroy: function() {
  17647. destroyObjectProperties(this, this.axis);
  17648. },
  17649. /**
  17650. * Renders the stack total label and adds it to the stack label group.
  17651. */
  17652. render: function(group) {
  17653. var options = this.options,
  17654. formatOption = options.format,
  17655. str = formatOption ?
  17656. format(formatOption, this) :
  17657. options.formatter.call(this); // format the text in the label
  17658. // Change the text to reflect the new total and set visibility to hidden
  17659. // in case the serie is hidden
  17660. if (this.label) {
  17661. this.label.attr({
  17662. text: str,
  17663. visibility: 'hidden'
  17664. });
  17665. // Create new label
  17666. } else {
  17667. this.label =
  17668. this.axis.chart.renderer.text(str, null, null, options.useHTML)
  17669. .css(options.style)
  17670. .attr({
  17671. align: this.textAlign,
  17672. rotation: options.rotation,
  17673. visibility: 'hidden' // hidden until setOffset is called
  17674. })
  17675. .add(group); // add to the labels-group
  17676. }
  17677. },
  17678. /**
  17679. * Sets the offset that the stack has from the x value and repositions the
  17680. * label.
  17681. */
  17682. setOffset: function(xOffset, xWidth) {
  17683. var stackItem = this,
  17684. axis = stackItem.axis,
  17685. chart = axis.chart,
  17686. inverted = chart.inverted,
  17687. reversed = axis.reversed,
  17688. neg = (this.isNegative && !reversed) ||
  17689. (!this.isNegative && reversed), // #4056
  17690. // stack value translated mapped to chart coordinates
  17691. y = axis.translate(
  17692. axis.usePercentage ? 100 : this.total,
  17693. 0,
  17694. 0,
  17695. 0,
  17696. 1
  17697. ),
  17698. yZero = axis.translate(0), // stack origin
  17699. h = Math.abs(y - yZero), // stack height
  17700. x = chart.xAxis[0].translate(this.x) + xOffset, // stack x position
  17701. plotHeight = chart.plotHeight,
  17702. stackBox = { // this is the box for the complete stack
  17703. x: inverted ? (neg ? y : y - h) : x,
  17704. y: inverted ?
  17705. plotHeight - x - xWidth : (neg ? (plotHeight - y - h) :
  17706. plotHeight - y),
  17707. width: inverted ? h : xWidth,
  17708. height: inverted ? xWidth : h
  17709. },
  17710. label = this.label,
  17711. alignAttr;
  17712. if (label) {
  17713. // Align the label to the box
  17714. label.align(this.alignOptions, null, stackBox);
  17715. // Set visibility (#678)
  17716. alignAttr = label.alignAttr;
  17717. label[
  17718. this.options.crop === false || chart.isInsidePlot(
  17719. alignAttr.x,
  17720. alignAttr.y
  17721. ) ? 'show' : 'hide'](true);
  17722. }
  17723. }
  17724. };
  17725. /**
  17726. * Generate stacks for each series and calculate stacks total values
  17727. */
  17728. Chart.prototype.getStacks = function() {
  17729. var chart = this;
  17730. // reset stacks for each yAxis
  17731. each(chart.yAxis, function(axis) {
  17732. if (axis.stacks && axis.hasVisibleSeries) {
  17733. axis.oldStacks = axis.stacks;
  17734. }
  17735. });
  17736. each(chart.series, function(series) {
  17737. if (series.options.stacking && (series.visible === true ||
  17738. chart.options.chart.ignoreHiddenSeries === false)) {
  17739. series.stackKey = series.type + pick(series.options.stack, '');
  17740. }
  17741. });
  17742. };
  17743. // Stacking methods defined on the Axis prototype
  17744. /**
  17745. * Build the stacks from top down
  17746. */
  17747. Axis.prototype.buildStacks = function() {
  17748. var axisSeries = this.series,
  17749. series,
  17750. reversedStacks = pick(this.options.reversedStacks, true),
  17751. len = axisSeries.length,
  17752. i;
  17753. if (!this.isXAxis) {
  17754. this.usePercentage = false;
  17755. i = len;
  17756. while (i--) {
  17757. axisSeries[reversedStacks ? i : len - i - 1].setStackedPoints();
  17758. }
  17759. i = len;
  17760. while (i--) {
  17761. series = axisSeries[reversedStacks ? i : len - i - 1];
  17762. if (series.setStackCliffs) {
  17763. series.setStackCliffs();
  17764. }
  17765. }
  17766. // Loop up again to compute percent stack
  17767. if (this.usePercentage) {
  17768. for (i = 0; i < len; i++) {
  17769. axisSeries[i].setPercentStacks();
  17770. }
  17771. }
  17772. }
  17773. };
  17774. Axis.prototype.renderStackTotals = function() {
  17775. var axis = this,
  17776. chart = axis.chart,
  17777. renderer = chart.renderer,
  17778. stacks = axis.stacks,
  17779. stackTotalGroup = axis.stackTotalGroup;
  17780. // Create a separate group for the stack total labels
  17781. if (!stackTotalGroup) {
  17782. axis.stackTotalGroup = stackTotalGroup =
  17783. renderer.g('stack-labels')
  17784. .attr({
  17785. visibility: 'visible',
  17786. zIndex: 6
  17787. })
  17788. .add();
  17789. }
  17790. // plotLeft/Top will change when y axis gets wider so we need to translate
  17791. // the stackTotalGroup at every render call. See bug #506 and #516
  17792. stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
  17793. // Render each stack total
  17794. objectEach(stacks, function(type) {
  17795. objectEach(type, function(stack) {
  17796. stack.render(stackTotalGroup);
  17797. });
  17798. });
  17799. };
  17800. /**
  17801. * Set all the stacks to initial states and destroy unused ones.
  17802. */
  17803. Axis.prototype.resetStacks = function() {
  17804. var axis = this,
  17805. stacks = axis.stacks;
  17806. if (!axis.isXAxis) {
  17807. objectEach(stacks, function(type) {
  17808. objectEach(type, function(stack, key) {
  17809. // Clean up memory after point deletion (#1044, #4320)
  17810. if (stack.touched < axis.stacksTouched) {
  17811. stack.destroy();
  17812. delete type[key];
  17813. // Reset stacks
  17814. } else {
  17815. stack.total = null;
  17816. stack.cum = null;
  17817. }
  17818. });
  17819. });
  17820. }
  17821. };
  17822. Axis.prototype.cleanStacks = function() {
  17823. var stacks;
  17824. if (!this.isXAxis) {
  17825. if (this.oldStacks) {
  17826. stacks = this.stacks = this.oldStacks;
  17827. }
  17828. // reset stacks
  17829. objectEach(stacks, function(type) {
  17830. objectEach(type, function(stack) {
  17831. stack.cum = stack.total;
  17832. });
  17833. });
  17834. }
  17835. };
  17836. // Stacking methods defnied for Series prototype
  17837. /**
  17838. * Adds series' points value to corresponding stack
  17839. */
  17840. Series.prototype.setStackedPoints = function() {
  17841. if (!this.options.stacking || (this.visible !== true &&
  17842. this.chart.options.chart.ignoreHiddenSeries !== false)) {
  17843. return;
  17844. }
  17845. var series = this,
  17846. xData = series.processedXData,
  17847. yData = series.processedYData,
  17848. stackedYData = [],
  17849. yDataLength = yData.length,
  17850. seriesOptions = series.options,
  17851. threshold = seriesOptions.threshold,
  17852. stackThreshold = seriesOptions.startFromThreshold ? threshold : 0,
  17853. stackOption = seriesOptions.stack,
  17854. stacking = seriesOptions.stacking,
  17855. stackKey = series.stackKey,
  17856. negKey = '-' + stackKey,
  17857. negStacks = series.negStacks,
  17858. yAxis = series.yAxis,
  17859. stacks = yAxis.stacks,
  17860. oldStacks = yAxis.oldStacks,
  17861. stackIndicator,
  17862. isNegative,
  17863. stack,
  17864. other,
  17865. key,
  17866. pointKey,
  17867. i,
  17868. x,
  17869. y;
  17870. yAxis.stacksTouched += 1;
  17871. // loop over the non-null y values and read them into a local array
  17872. for (i = 0; i < yDataLength; i++) {
  17873. x = xData[i];
  17874. y = yData[i];
  17875. stackIndicator = series.getStackIndicator(
  17876. stackIndicator,
  17877. x,
  17878. series.index
  17879. );
  17880. pointKey = stackIndicator.key;
  17881. // Read stacked values into a stack based on the x value,
  17882. // the sign of y and the stack key. Stacking is also handled for null
  17883. // values (#739)
  17884. isNegative = negStacks && y < (stackThreshold ? 0 : threshold);
  17885. key = isNegative ? negKey : stackKey;
  17886. // Create empty object for this stack if it doesn't exist yet
  17887. if (!stacks[key]) {
  17888. stacks[key] = {};
  17889. }
  17890. // Initialize StackItem for this x
  17891. if (!stacks[key][x]) {
  17892. if (oldStacks[key] && oldStacks[key][x]) {
  17893. stacks[key][x] = oldStacks[key][x];
  17894. stacks[key][x].total = null;
  17895. } else {
  17896. stacks[key][x] = new StackItem(
  17897. yAxis,
  17898. yAxis.options.stackLabels,
  17899. isNegative,
  17900. x,
  17901. stackOption
  17902. );
  17903. }
  17904. }
  17905. // If the StackItem doesn't exist, create it first
  17906. stack = stacks[key][x];
  17907. if (y !== null) {
  17908. stack.points[pointKey] = stack.points[series.index] = [pick(stack.cum, stackThreshold)];
  17909. // Record the base of the stack
  17910. if (!defined(stack.cum)) {
  17911. stack.base = pointKey;
  17912. }
  17913. stack.touched = yAxis.stacksTouched;
  17914. // In area charts, if there are multiple points on the same X value,
  17915. // let the area fill the full span of those points
  17916. if (stackIndicator.index > 0 && series.singleStacks === false) {
  17917. stack.points[pointKey][0] =
  17918. stack.points[series.index + ',' + x + ',0'][0];
  17919. }
  17920. }
  17921. // Add value to the stack total
  17922. if (stacking === 'percent') {
  17923. // Percent stacked column, totals are the same for the positive and
  17924. // negative stacks
  17925. other = isNegative ? stackKey : negKey;
  17926. if (negStacks && stacks[other] && stacks[other][x]) {
  17927. other = stacks[other][x];
  17928. stack.total = other.total =
  17929. Math.max(other.total, stack.total) + Math.abs(y) || 0;
  17930. // Percent stacked areas
  17931. } else {
  17932. stack.total = correctFloat(stack.total + (Math.abs(y) || 0));
  17933. }
  17934. } else {
  17935. stack.total = correctFloat(stack.total + (y || 0));
  17936. }
  17937. stack.cum = pick(stack.cum, stackThreshold) + (y || 0);
  17938. if (y !== null) {
  17939. stack.points[pointKey].push(stack.cum);
  17940. stackedYData[i] = stack.cum;
  17941. }
  17942. }
  17943. if (stacking === 'percent') {
  17944. yAxis.usePercentage = true;
  17945. }
  17946. this.stackedYData = stackedYData; // To be used in getExtremes
  17947. // Reset old stacks
  17948. yAxis.oldStacks = {};
  17949. };
  17950. /**
  17951. * Iterate over all stacks and compute the absolute values to percent
  17952. */
  17953. Series.prototype.setPercentStacks = function() {
  17954. var series = this,
  17955. stackKey = series.stackKey,
  17956. stacks = series.yAxis.stacks,
  17957. processedXData = series.processedXData,
  17958. stackIndicator;
  17959. each([stackKey, '-' + stackKey], function(key) {
  17960. var i = processedXData.length,
  17961. x,
  17962. stack,
  17963. pointExtremes,
  17964. totalFactor;
  17965. while (i--) {
  17966. x = processedXData[i];
  17967. stackIndicator = series.getStackIndicator(
  17968. stackIndicator,
  17969. x,
  17970. series.index,
  17971. key
  17972. );
  17973. stack = stacks[key] && stacks[key][x];
  17974. pointExtremes = stack && stack.points[stackIndicator.key];
  17975. if (pointExtremes) {
  17976. totalFactor = stack.total ? 100 / stack.total : 0;
  17977. // Y bottom value
  17978. pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor);
  17979. // Y value
  17980. pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor);
  17981. series.stackedYData[i] = pointExtremes[1];
  17982. }
  17983. }
  17984. });
  17985. };
  17986. /**
  17987. * Get stack indicator, according to it's x-value, to determine points with the
  17988. * same x-value
  17989. */
  17990. Series.prototype.getStackIndicator = function(stackIndicator, x, index, key) {
  17991. // Update stack indicator, when:
  17992. // first point in a stack || x changed || stack type (negative vs positive)
  17993. // changed:
  17994. if (!defined(stackIndicator) || stackIndicator.x !== x ||
  17995. (key && stackIndicator.key !== key)) {
  17996. stackIndicator = {
  17997. x: x,
  17998. index: 0,
  17999. key: key
  18000. };
  18001. } else {
  18002. stackIndicator.index++;
  18003. }
  18004. stackIndicator.key = [index, x, stackIndicator.index].join(',');
  18005. return stackIndicator;
  18006. };
  18007. }(Highcharts));
  18008. (function(H) {
  18009. /**
  18010. * (c) 2010-2017 Torstein Honsi
  18011. *
  18012. * License: www.highcharts.com/license
  18013. */
  18014. var addEvent = H.addEvent,
  18015. animate = H.animate,
  18016. Axis = H.Axis,
  18017. Chart = H.Chart,
  18018. createElement = H.createElement,
  18019. css = H.css,
  18020. defined = H.defined,
  18021. each = H.each,
  18022. erase = H.erase,
  18023. extend = H.extend,
  18024. fireEvent = H.fireEvent,
  18025. inArray = H.inArray,
  18026. isNumber = H.isNumber,
  18027. isObject = H.isObject,
  18028. isArray = H.isArray,
  18029. merge = H.merge,
  18030. objectEach = H.objectEach,
  18031. pick = H.pick,
  18032. Point = H.Point,
  18033. Series = H.Series,
  18034. seriesTypes = H.seriesTypes,
  18035. setAnimation = H.setAnimation,
  18036. splat = H.splat;
  18037. // Extend the Chart prototype for dynamic methods
  18038. extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
  18039. /**
  18040. * Add a series to the chart after render time. Note that this method should
  18041. * never be used when adding data synchronously at chart render time, as it
  18042. * adds expense to the calculations and rendering. When adding data at the
  18043. * same time as the chart is initiated, add the series as a configuration
  18044. * option instead. With multiple axes, the `offset` is dynamically adjusted.
  18045. *
  18046. * @param {SeriesOptions} options
  18047. * The config options for the series.
  18048. * @param {Boolean} [redraw=true]
  18049. * Whether to redraw the chart after adding.
  18050. * @param {AnimationOptions} animation
  18051. * Whether to apply animation, and optionally animation
  18052. * configuration.
  18053. *
  18054. * @return {Highcharts.Series}
  18055. * The newly created series object.
  18056. *
  18057. * @sample highcharts/members/chart-addseries/
  18058. * Add a series from a button
  18059. * @sample stock/members/chart-addseries/
  18060. * Add a series in Highstock
  18061. */
  18062. addSeries: function(options, redraw, animation) {
  18063. var series,
  18064. chart = this;
  18065. if (options) {
  18066. redraw = pick(redraw, true); // defaults to true
  18067. fireEvent(chart, 'addSeries', {
  18068. options: options
  18069. }, function() {
  18070. series = chart.initSeries(options);
  18071. chart.isDirtyLegend = true; // the series array is out of sync with the display
  18072. chart.linkSeries();
  18073. if (redraw) {
  18074. chart.redraw(animation);
  18075. }
  18076. });
  18077. }
  18078. return series;
  18079. },
  18080. /**
  18081. * Add an axis to the chart after render time. Note that this method should
  18082. * never be used when adding data synchronously at chart render time, as it
  18083. * adds expense to the calculations and rendering. When adding data at the
  18084. * same time as the chart is initiated, add the axis as a configuration
  18085. * option instead.
  18086. * @param {AxisOptions} options
  18087. * The axis options.
  18088. * @param {Boolean} [isX=false]
  18089. * Whether it is an X axis or a value axis.
  18090. * @param {Boolean} [redraw=true]
  18091. * Whether to redraw the chart after adding.
  18092. * @param {AnimationOptions} [animation=true]
  18093. * Whether and how to apply animation in the redraw.
  18094. *
  18095. * @sample highcharts/members/chart-addaxis/ Add and remove axes
  18096. */
  18097. addAxis: function(options, isX, redraw, animation) {
  18098. var key = isX ? 'xAxis' : 'yAxis',
  18099. chartOptions = this.options,
  18100. userOptions = merge(options, {
  18101. index: this[key].length,
  18102. isX: isX
  18103. });
  18104. new Axis(this, userOptions); // eslint-disable-line no-new
  18105. // Push the new axis options to the chart options
  18106. chartOptions[key] = splat(chartOptions[key] || {});
  18107. chartOptions[key].push(userOptions);
  18108. if (pick(redraw, true)) {
  18109. this.redraw(animation);
  18110. }
  18111. },
  18112. /**
  18113. * Dim the chart and show a loading text or symbol. Options for the loading
  18114. * screen are defined in {@link
  18115. * https://api.highcharts.com/highcharts/loading|the loading options}.
  18116. *
  18117. * @param {String} str
  18118. * An optional text to show in the loading label instead of the
  18119. * default one. The default text is set in {@link
  18120. * http://api.highcharts.com/highcharts/lang.loading|lang.loading}.
  18121. *
  18122. * @sample highcharts/members/chart-hideloading/
  18123. * Show and hide loading from a button
  18124. * @sample highcharts/members/chart-showloading/
  18125. * Apply different text labels
  18126. * @sample stock/members/chart-show-hide-loading/
  18127. * Toggle loading in Highstock
  18128. */
  18129. showLoading: function(str) {
  18130. var chart = this,
  18131. options = chart.options,
  18132. loadingDiv = chart.loadingDiv,
  18133. loadingOptions = options.loading,
  18134. setLoadingSize = function() {
  18135. if (loadingDiv) {
  18136. css(loadingDiv, {
  18137. left: chart.plotLeft + 'px',
  18138. top: chart.plotTop + 'px',
  18139. width: chart.plotWidth + 'px',
  18140. height: chart.plotHeight + 'px'
  18141. });
  18142. }
  18143. };
  18144. // create the layer at the first call
  18145. if (!loadingDiv) {
  18146. chart.loadingDiv = loadingDiv = createElement('div', {
  18147. className: 'highcharts-loading highcharts-loading-hidden'
  18148. }, null, chart.container);
  18149. chart.loadingSpan = createElement(
  18150. 'span', {
  18151. className: 'highcharts-loading-inner'
  18152. },
  18153. null,
  18154. loadingDiv
  18155. );
  18156. addEvent(chart, 'redraw', setLoadingSize); // #1080
  18157. }
  18158. loadingDiv.className = 'highcharts-loading';
  18159. // Update text
  18160. chart.loadingSpan.innerHTML = str || options.lang.loading;
  18161. // Update visuals
  18162. css(loadingDiv, extend(loadingOptions.style, {
  18163. zIndex: 10
  18164. }));
  18165. css(chart.loadingSpan, loadingOptions.labelStyle);
  18166. // Show it
  18167. if (!chart.loadingShown) {
  18168. css(loadingDiv, {
  18169. opacity: 0,
  18170. display: ''
  18171. });
  18172. animate(loadingDiv, {
  18173. opacity: loadingOptions.style.opacity || 0.5
  18174. }, {
  18175. duration: loadingOptions.showDuration || 0
  18176. });
  18177. }
  18178. chart.loadingShown = true;
  18179. setLoadingSize();
  18180. },
  18181. /**
  18182. * Hide the loading layer.
  18183. *
  18184. * @see Highcharts.Chart#showLoading
  18185. * @sample highcharts/members/chart-hideloading/
  18186. * Show and hide loading from a button
  18187. * @sample stock/members/chart-show-hide-loading/
  18188. * Toggle loading in Highstock
  18189. */
  18190. hideLoading: function() {
  18191. var options = this.options,
  18192. loadingDiv = this.loadingDiv;
  18193. if (loadingDiv) {
  18194. loadingDiv.className = 'highcharts-loading highcharts-loading-hidden';
  18195. animate(loadingDiv, {
  18196. opacity: 0
  18197. }, {
  18198. duration: options.loading.hideDuration || 100,
  18199. complete: function() {
  18200. css(loadingDiv, {
  18201. display: 'none'
  18202. });
  18203. }
  18204. });
  18205. }
  18206. this.loadingShown = false;
  18207. },
  18208. /**
  18209. * These properties cause isDirtyBox to be set to true when updating. Can be extended from plugins.
  18210. */
  18211. propsRequireDirtyBox: ['backgroundColor', 'borderColor', 'borderWidth', 'margin', 'marginTop', 'marginRight',
  18212. 'marginBottom', 'marginLeft', 'spacing', 'spacingTop', 'spacingRight', 'spacingBottom', 'spacingLeft',
  18213. 'borderRadius', 'plotBackgroundColor', 'plotBackgroundImage', 'plotBorderColor', 'plotBorderWidth',
  18214. 'plotShadow', 'shadow'
  18215. ],
  18216. /**
  18217. * These properties cause all series to be updated when updating. Can be
  18218. * extended from plugins.
  18219. */
  18220. propsRequireUpdateSeries: ['chart.inverted', 'chart.polar',
  18221. 'chart.ignoreHiddenSeries', 'chart.type', 'colors', 'plotOptions',
  18222. 'tooltip'
  18223. ],
  18224. /**
  18225. * A generic function to update any element of the chart. Elements can be
  18226. * enabled and disabled, moved, re-styled, re-formatted etc.
  18227. *
  18228. * A special case is configuration objects that take arrays, for example
  18229. * {@link https://api.highcharts.com/highcharts/xAxis|xAxis},
  18230. * {@link https://api.highcharts.com/highcharts/yAxis|yAxis} or
  18231. * {@link https://api.highcharts.com/highcharts/series|series}. For these
  18232. * collections, an `id` option is used to map the new option set to an
  18233. * existing object. If an existing object of the same id is not found, the
  18234. * corresponding item is updated. So for example, running `chart.update`
  18235. * with a series item without an id, will cause the existing chart's series
  18236. * with the same index in the series array to be updated.
  18237. *
  18238. * See also the {@link https://api.highcharts.com/highcharts/responsive|
  18239. * responsive option set}. Switching between `responsive.rules` basically
  18240. * runs `chart.update` under the hood.
  18241. *
  18242. * @param {Options} options
  18243. * A configuration object for the new chart options.
  18244. * @param {Boolean} [redraw=true]
  18245. * Whether to redraw the chart.
  18246. *
  18247. * @sample highcharts/members/chart-update/
  18248. * Update chart geometry
  18249. */
  18250. update: function(options, redraw) {
  18251. var chart = this,
  18252. adders = {
  18253. credits: 'addCredits',
  18254. title: 'setTitle',
  18255. subtitle: 'setSubtitle'
  18256. },
  18257. optionsChart = options.chart,
  18258. updateAllAxes,
  18259. updateAllSeries,
  18260. newWidth,
  18261. newHeight;
  18262. // If the top-level chart option is present, some special updates are required
  18263. if (optionsChart) {
  18264. merge(true, chart.options.chart, optionsChart);
  18265. // Setter function
  18266. if ('className' in optionsChart) {
  18267. chart.setClassName(optionsChart.className);
  18268. }
  18269. if ('inverted' in optionsChart || 'polar' in optionsChart) {
  18270. // Parse options.chart.inverted and options.chart.polar together
  18271. // with the available series.
  18272. chart.propFromSeries();
  18273. updateAllAxes = true;
  18274. }
  18275. if ('alignTicks' in optionsChart) { // #6452
  18276. updateAllAxes = true;
  18277. }
  18278. objectEach(optionsChart, function(val, key) {
  18279. if (inArray('chart.' + key, chart.propsRequireUpdateSeries) !== -1) {
  18280. updateAllSeries = true;
  18281. }
  18282. // Only dirty box
  18283. if (inArray(key, chart.propsRequireDirtyBox) !== -1) {
  18284. chart.isDirtyBox = true;
  18285. }
  18286. });
  18287. if ('style' in optionsChart) {
  18288. chart.renderer.setStyle(optionsChart.style);
  18289. }
  18290. }
  18291. // Moved up, because tooltip needs updated plotOptions (#6218)
  18292. if (options.colors) {
  18293. this.options.colors = options.colors;
  18294. }
  18295. if (options.plotOptions) {
  18296. merge(true, this.options.plotOptions, options.plotOptions);
  18297. }
  18298. // Some option stuctures correspond one-to-one to chart objects that
  18299. // have update methods, for example
  18300. // options.credits => chart.credits
  18301. // options.legend => chart.legend
  18302. // options.title => chart.title
  18303. // options.tooltip => chart.tooltip
  18304. // options.subtitle => chart.subtitle
  18305. // options.mapNavigation => chart.mapNavigation
  18306. // options.navigator => chart.navigator
  18307. // options.scrollbar => chart.scrollbar
  18308. objectEach(options, function(val, key) {
  18309. if (chart[key] && typeof chart[key].update === 'function') {
  18310. chart[key].update(val, false);
  18311. // If a one-to-one object does not exist, look for an adder function
  18312. } else if (typeof chart[adders[key]] === 'function') {
  18313. chart[adders[key]](val);
  18314. }
  18315. if (
  18316. key !== 'chart' &&
  18317. inArray(key, chart.propsRequireUpdateSeries) !== -1
  18318. ) {
  18319. updateAllSeries = true;
  18320. }
  18321. });
  18322. // Setters for collections. For axes and series, each item is referred
  18323. // by an id. If the id is not found, it defaults to the corresponding
  18324. // item in the collection, so setting one series without an id, will
  18325. // update the first series in the chart. Setting two series without
  18326. // an id will update the first and the second respectively (#6019)
  18327. // chart.update and responsive.
  18328. each([
  18329. 'xAxis',
  18330. 'yAxis',
  18331. 'zAxis',
  18332. 'series',
  18333. 'colorAxis',
  18334. 'pane'
  18335. ], function(coll) {
  18336. if (options[coll]) {
  18337. each(splat(options[coll]), function(newOptions, i) {
  18338. var item = (
  18339. defined(newOptions.id) &&
  18340. chart.get(newOptions.id)
  18341. ) || chart[coll][i];
  18342. if (item && item.coll === coll) {
  18343. item.update(newOptions, false);
  18344. }
  18345. });
  18346. }
  18347. });
  18348. if (updateAllAxes) {
  18349. each(chart.axes, function(axis) {
  18350. axis.update({}, false);
  18351. });
  18352. }
  18353. // Certain options require the whole series structure to be thrown away
  18354. // and rebuilt
  18355. if (updateAllSeries) {
  18356. each(chart.series, function(series) {
  18357. series.update({}, false);
  18358. });
  18359. }
  18360. // For loading, just update the options, do not redraw
  18361. if (options.loading) {
  18362. merge(true, chart.options.loading, options.loading);
  18363. }
  18364. // Update size. Redraw is forced.
  18365. newWidth = optionsChart && optionsChart.width;
  18366. newHeight = optionsChart && optionsChart.height;
  18367. if ((isNumber(newWidth) && newWidth !== chart.chartWidth) ||
  18368. (isNumber(newHeight) && newHeight !== chart.chartHeight)) {
  18369. chart.setSize(newWidth, newHeight);
  18370. } else if (pick(redraw, true)) {
  18371. chart.redraw();
  18372. }
  18373. },
  18374. /**
  18375. * Setter function to allow use from chart.update
  18376. */
  18377. setSubtitle: function(options) {
  18378. this.setTitle(undefined, options);
  18379. }
  18380. });
  18381. // extend the Point prototype for dynamic methods
  18382. extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
  18383. /**
  18384. * Update point with new options (typically x/y data) and optionally redraw
  18385. * the series.
  18386. *
  18387. * @param {Object} options
  18388. * The point options. Point options are handled as described under
  18389. * the `series<type>.data` item for each series type. For example
  18390. * for a line series, if options is a single number, the point will
  18391. * be given that number as the main y value. If it is an array, it
  18392. * will be interpreted as x and y values respectively. If it is an
  18393. * object, advanced options are applied.
  18394. * @param {Boolean} [redraw=true]
  18395. * Whether to redraw the chart after the point is updated. If doing
  18396. * more operations on the chart, it is best practice to set
  18397. * `redraw` to false and call `chart.redraw()` after.
  18398. * @param {AnimationOptions} [animation=true]
  18399. * Whether to apply animation, and optionally animation
  18400. * configuration.
  18401. *
  18402. * @sample highcharts/members/point-update-column/
  18403. * Update column value
  18404. * @sample highcharts/members/point-update-pie/
  18405. * Update pie slice
  18406. * @sample maps/members/point-update/
  18407. * Update map area value in Highmaps
  18408. */
  18409. update: function(options, redraw, animation, runEvent) {
  18410. var point = this,
  18411. series = point.series,
  18412. graphic = point.graphic,
  18413. i,
  18414. chart = series.chart,
  18415. seriesOptions = series.options;
  18416. redraw = pick(redraw, true);
  18417. function update() {
  18418. point.applyOptions(options);
  18419. // Update visuals
  18420. if (point.y === null && graphic) { // #4146
  18421. point.graphic = graphic.destroy();
  18422. }
  18423. if (isObject(options, true)) {
  18424. // Destroy so we can get new elements
  18425. if (graphic && graphic.element) {
  18426. if (options && options.marker && options.marker.symbol) {
  18427. point.graphic = graphic.destroy();
  18428. }
  18429. }
  18430. if (options && options.dataLabels && point.dataLabel) { // #2468
  18431. point.dataLabel = point.dataLabel.destroy();
  18432. }
  18433. }
  18434. // record changes in the parallel arrays
  18435. i = point.index;
  18436. series.updateParallelArrays(point, i);
  18437. // Record the options to options.data. If the old or the new config
  18438. // is an object, use point options, otherwise use raw options
  18439. // (#4701, #4916).
  18440. seriesOptions.data[i] = (
  18441. isObject(seriesOptions.data[i], true) ||
  18442. isObject(options, true)
  18443. ) ?
  18444. point.options :
  18445. options;
  18446. // redraw
  18447. series.isDirty = series.isDirtyData = true;
  18448. if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320
  18449. chart.isDirtyBox = true;
  18450. }
  18451. if (seriesOptions.legendType === 'point') { // #1831, #1885
  18452. chart.isDirtyLegend = true;
  18453. }
  18454. if (redraw) {
  18455. chart.redraw(animation);
  18456. }
  18457. }
  18458. // Fire the event with a default handler of doing the update
  18459. if (runEvent === false) { // When called from setData
  18460. update();
  18461. } else {
  18462. point.firePointEvent('update', {
  18463. options: options
  18464. }, update);
  18465. }
  18466. },
  18467. /**
  18468. * Remove a point and optionally redraw the series and if necessary the axes
  18469. * @param {Boolean} redraw
  18470. * Whether to redraw the chart or wait for an explicit call. When
  18471. * doing more operations on the chart, for example running
  18472. * `point.remove()` in a loop, it is best practice to set `redraw`
  18473. * to false and call `chart.redraw()` after.
  18474. * @param {AnimationOptions} [animation=false]
  18475. * Whether to apply animation, and optionally animation
  18476. * configuration.
  18477. *
  18478. * @sample highcharts/plotoptions/series-point-events-remove/
  18479. * Remove point and confirm
  18480. * @sample highcharts/members/point-remove/
  18481. * Remove pie slice
  18482. * @sample maps/members/point-remove/
  18483. * Remove selected points in Highmaps
  18484. */
  18485. remove: function(redraw, animation) {
  18486. this.series.removePoint(inArray(this, this.series.data), redraw, animation);
  18487. }
  18488. });
  18489. // Extend the series prototype for dynamic methods
  18490. extend(Series.prototype, /** @lends Series.prototype */ {
  18491. /**
  18492. * Add a point to the series after render time. The point can be added at
  18493. * the end, or by giving it an X value, to the start or in the middle of the
  18494. * series.
  18495. *
  18496. * @param {Number|Array|Object} options
  18497. * The point options. If options is a single number, a point with
  18498. * that y value is appended to the series.If it is an array, it will
  18499. * be interpreted as x and y values respectively. If it is an
  18500. * object, advanced options as outlined under `series.data` are
  18501. * applied.
  18502. * @param {Boolean} [redraw=true]
  18503. * Whether to redraw the chart after the point is added. When adding
  18504. * more than one point, it is highly recommended that the redraw
  18505. * option be set to false, and instead {@link Chart#redraw}
  18506. * is explicitly called after the adding of points is finished.
  18507. * Otherwise, the chart will redraw after adding each point.
  18508. * @param {Boolean} [shift=false]
  18509. * If true, a point is shifted off the start of the series as one is
  18510. * appended to the end.
  18511. * @param {AnimationOptions} [animation]
  18512. * Whether to apply animation, and optionally animation
  18513. * configuration.
  18514. *
  18515. * @sample highcharts/members/series-addpoint-append/
  18516. * Append point
  18517. * @sample highcharts/members/series-addpoint-append-and-shift/
  18518. * Append and shift
  18519. * @sample highcharts/members/series-addpoint-x-and-y/
  18520. * Both X and Y values given
  18521. * @sample highcharts/members/series-addpoint-pie/
  18522. * Append pie slice
  18523. * @sample stock/members/series-addpoint/
  18524. * Append 100 points in Highstock
  18525. * @sample stock/members/series-addpoint-shift/
  18526. * Append and shift in Highstock
  18527. * @sample maps/members/series-addpoint/
  18528. * Add a point in Highmaps
  18529. */
  18530. addPoint: function(options, redraw, shift, animation) {
  18531. var series = this,
  18532. seriesOptions = series.options,
  18533. data = series.data,
  18534. chart = series.chart,
  18535. xAxis = series.xAxis,
  18536. names = xAxis && xAxis.hasNames && xAxis.names,
  18537. dataOptions = seriesOptions.data,
  18538. point,
  18539. isInTheMiddle,
  18540. xData = series.xData,
  18541. i,
  18542. x;
  18543. // Optional redraw, defaults to true
  18544. redraw = pick(redraw, true);
  18545. // Get options and push the point to xData, yData and series.options. In series.generatePoints
  18546. // the Point instance will be created on demand and pushed to the series.data array.
  18547. point = {
  18548. series: series
  18549. };
  18550. series.pointClass.prototype.applyOptions.apply(point, [options]);
  18551. x = point.x;
  18552. // Get the insertion point
  18553. i = xData.length;
  18554. if (series.requireSorting && x < xData[i - 1]) {
  18555. isInTheMiddle = true;
  18556. while (i && xData[i - 1] > x) {
  18557. i--;
  18558. }
  18559. }
  18560. series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item
  18561. series.updateParallelArrays(point, i); // update it
  18562. if (names && point.name) {
  18563. names[x] = point.name;
  18564. }
  18565. dataOptions.splice(i, 0, options);
  18566. if (isInTheMiddle) {
  18567. series.data.splice(i, 0, null);
  18568. series.processData();
  18569. }
  18570. // Generate points to be added to the legend (#1329)
  18571. if (seriesOptions.legendType === 'point') {
  18572. series.generatePoints();
  18573. }
  18574. // Shift the first point off the parallel arrays
  18575. if (shift) {
  18576. if (data[0] && data[0].remove) {
  18577. data[0].remove(false);
  18578. } else {
  18579. data.shift();
  18580. series.updateParallelArrays(point, 'shift');
  18581. dataOptions.shift();
  18582. }
  18583. }
  18584. // redraw
  18585. series.isDirty = true;
  18586. series.isDirtyData = true;
  18587. if (redraw) {
  18588. chart.redraw(animation); // Animation is set anyway on redraw, #5665
  18589. }
  18590. },
  18591. /**
  18592. * Remove a point from the series. Unlike the {@link Highcharts.Point#remove}
  18593. * method, this can also be done on a point that is not instanciated because
  18594. * it is outside the view or subject to Highstock data grouping.
  18595. *
  18596. * @param {Number} i
  18597. * The index of the point in the {@link Highcharts.Series.data|data}
  18598. * array.
  18599. * @param {Boolean} [redraw=true]
  18600. * Whether to redraw the chart after the point is added. When
  18601. * removing more than one point, it is highly recommended that the
  18602. * `redraw` option be set to `false`, and instead {@link
  18603. * Highcharts.Chart#redraw} is explicitly called after the adding of
  18604. * points is finished.
  18605. * @param {AnimationOptions} [animation]
  18606. * Whether and optionally how the series should be animated.
  18607. *
  18608. * @sample highcharts/members/series-removepoint/
  18609. * Remove cropped point
  18610. */
  18611. removePoint: function(i, redraw, animation) {
  18612. var series = this,
  18613. data = series.data,
  18614. point = data[i],
  18615. points = series.points,
  18616. chart = series.chart,
  18617. remove = function() {
  18618. if (points && points.length === data.length) { // #4935
  18619. points.splice(i, 1);
  18620. }
  18621. data.splice(i, 1);
  18622. series.options.data.splice(i, 1);
  18623. series.updateParallelArrays(point || {
  18624. series: series
  18625. }, 'splice', i, 1);
  18626. if (point) {
  18627. point.destroy();
  18628. }
  18629. // redraw
  18630. series.isDirty = true;
  18631. series.isDirtyData = true;
  18632. if (redraw) {
  18633. chart.redraw();
  18634. }
  18635. };
  18636. setAnimation(animation, chart);
  18637. redraw = pick(redraw, true);
  18638. // Fire the event with a default handler of removing the point
  18639. if (point) {
  18640. point.firePointEvent('remove', null, remove);
  18641. } else {
  18642. remove();
  18643. }
  18644. },
  18645. /**
  18646. * Remove a series and optionally redraw the chart.
  18647. *
  18648. * @param {Boolean} [redraw=true]
  18649. * Whether to redraw the chart or wait for an explicit call to
  18650. * {@link Highcharts.Chart#redraw}.
  18651. * @param {AnimationOptions} [animation]
  18652. * Whether to apply animation, and optionally animation
  18653. * configuration
  18654. * @param {Boolean} [withEvent=true]
  18655. * Used internally, whether to fire the series `remove` event.
  18656. *
  18657. * @sample highcharts/members/series-remove/
  18658. * Remove first series from a button
  18659. */
  18660. remove: function(redraw, animation, withEvent) {
  18661. var series = this,
  18662. chart = series.chart;
  18663. function remove() {
  18664. // Destroy elements
  18665. series.destroy();
  18666. // Redraw
  18667. chart.isDirtyLegend = chart.isDirtyBox = true;
  18668. chart.linkSeries();
  18669. if (pick(redraw, true)) {
  18670. chart.redraw(animation);
  18671. }
  18672. }
  18673. // Fire the event with a default handler of removing the point
  18674. if (withEvent !== false) {
  18675. fireEvent(series, 'remove', null, remove);
  18676. } else {
  18677. remove();
  18678. }
  18679. },
  18680. /**
  18681. * Update the series with a new set of options. For a clean and precise
  18682. * handling of new options, all methods and elements from the series are
  18683. * removed, and it is initiated from scratch. Therefore, this method is more
  18684. * performance expensive than some other utility methods like {@link
  18685. * Series#setData} or {@link Series#setVisible}.
  18686. *
  18687. * @param {SeriesOptions} options
  18688. * New options that will be merged with the series' existing
  18689. * options.
  18690. * @param {Boolean} [redraw=true]
  18691. * Whether to redraw the chart after the series is altered. If doing
  18692. * more operations on the chart, it is a good idea to set redraw to
  18693. * false and call {@link Chart#redraw} after.
  18694. *
  18695. * @sample highcharts/members/series-update/
  18696. * Updating series options
  18697. * @sample maps/members/series-update/
  18698. * Update series options in Highmaps
  18699. */
  18700. update: function(newOptions, redraw) {
  18701. var series = this,
  18702. chart = series.chart,
  18703. // must use user options when changing type because series.options
  18704. // is merged in with type specific plotOptions
  18705. oldOptions = series.userOptions,
  18706. oldType = series.oldType || series.type,
  18707. newType = newOptions.type || oldOptions.type || chart.options.chart.type,
  18708. proto = seriesTypes[oldType].prototype,
  18709. preserve = ['group', 'markerGroup', 'dataLabelsGroup'],
  18710. n;
  18711. // Running Series.update to update the data only is an intuitive usage,
  18712. // so we want to make sure that when used like this, we run the
  18713. // cheaper setData function and allow animation instead of completely
  18714. // recreating the series instance.
  18715. if (Object.keys && Object.keys(newOptions).toString() === 'data') {
  18716. return this.setData(newOptions.data, redraw);
  18717. }
  18718. // If we're changing type or zIndex, create new groups (#3380, #3404)
  18719. if ((newType && newType !== oldType) || newOptions.zIndex !== undefined) {
  18720. preserve.length = 0;
  18721. }
  18722. // Make sure groups are not destroyed (#3094)
  18723. each(preserve, function(prop) {
  18724. preserve[prop] = series[prop];
  18725. delete series[prop];
  18726. });
  18727. // Do the merge, with some forced options
  18728. newOptions = merge(oldOptions, {
  18729. animation: false,
  18730. index: series.index,
  18731. pointStart: series.xData[0] // when updating after addPoint
  18732. }, {
  18733. data: series.options.data
  18734. }, newOptions);
  18735. // Destroy the series and delete all properties. Reinsert all methods
  18736. // and properties from the new type prototype (#2270, #3719)
  18737. series.remove(false, null, false);
  18738. for (n in proto) {
  18739. series[n] = undefined;
  18740. }
  18741. extend(series, seriesTypes[newType || oldType].prototype);
  18742. // Re-register groups (#3094)
  18743. each(preserve, function(prop) {
  18744. series[prop] = preserve[prop];
  18745. });
  18746. series.init(chart, newOptions);
  18747. series.oldType = oldType;
  18748. chart.linkSeries(); // Links are lost in series.remove (#3028)
  18749. if (pick(redraw, true)) {
  18750. chart.redraw(false);
  18751. }
  18752. }
  18753. });
  18754. // Extend the Axis.prototype for dynamic methods
  18755. extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
  18756. /**
  18757. * Update an axis object with a new set of options. The options are merged
  18758. * with the existing options, so only new or altered options need to be
  18759. * specified.
  18760. *
  18761. * @param {Object} options
  18762. * The new options that will be merged in with existing options on
  18763. * the axis.
  18764. * @sample highcharts/members/axis-update/ Axis update demo
  18765. */
  18766. update: function(options, redraw) {
  18767. var chart = this.chart;
  18768. options = chart.options[this.coll][this.options.index] =
  18769. merge(this.userOptions, options);
  18770. this.destroy(true);
  18771. this.init(chart, extend(options, {
  18772. events: undefined
  18773. }));
  18774. chart.isDirtyBox = true;
  18775. if (pick(redraw, true)) {
  18776. chart.redraw();
  18777. }
  18778. },
  18779. /**
  18780. * Remove the axis from the chart.
  18781. *
  18782. * @param {Boolean} [redraw=true] Whether to redraw the chart following the
  18783. * remove.
  18784. *
  18785. * @sample highcharts/members/chart-addaxis/ Add and remove axes
  18786. */
  18787. remove: function(redraw) {
  18788. var chart = this.chart,
  18789. key = this.coll, // xAxis or yAxis
  18790. axisSeries = this.series,
  18791. i = axisSeries.length;
  18792. // Remove associated series (#2687)
  18793. while (i--) {
  18794. if (axisSeries[i]) {
  18795. axisSeries[i].remove(false);
  18796. }
  18797. }
  18798. // Remove the axis
  18799. erase(chart.axes, this);
  18800. erase(chart[key], this);
  18801. if (isArray(chart.options[key])) {
  18802. chart.options[key].splice(this.options.index, 1);
  18803. } else { // color axis, #6488
  18804. delete chart.options[key];
  18805. }
  18806. each(chart[key], function(axis, i) { // Re-index, #1706
  18807. axis.options.index = i;
  18808. });
  18809. this.destroy();
  18810. chart.isDirtyBox = true;
  18811. if (pick(redraw, true)) {
  18812. chart.redraw();
  18813. }
  18814. },
  18815. /**
  18816. * Update the axis title by options after render time.
  18817. *
  18818. * @param {TitleOptions} titleOptions
  18819. * The additional title options.
  18820. * @param {Boolean} [redraw=true]
  18821. * Whether to redraw the chart after setting the title.
  18822. * @sample highcharts/members/axis-settitle/ Set a new Y axis title
  18823. */
  18824. setTitle: function(titleOptions, redraw) {
  18825. this.update({
  18826. title: titleOptions
  18827. }, redraw);
  18828. },
  18829. /**
  18830. * Set new axis categories and optionally redraw.
  18831. * @param {Array.<String>} categories - The new categories.
  18832. * @param {Boolean} [redraw=true] - Whether to redraw the chart.
  18833. * @sample highcharts/members/axis-setcategories/ Set categories by click on
  18834. * a button
  18835. */
  18836. setCategories: function(categories, redraw) {
  18837. this.update({
  18838. categories: categories
  18839. }, redraw);
  18840. }
  18841. });
  18842. }(Highcharts));
  18843. (function(H) {
  18844. /**
  18845. * (c) 2010-2017 Torstein Honsi
  18846. *
  18847. * License: www.highcharts.com/license
  18848. */
  18849. var color = H.color,
  18850. each = H.each,
  18851. LegendSymbolMixin = H.LegendSymbolMixin,
  18852. map = H.map,
  18853. pick = H.pick,
  18854. Series = H.Series,
  18855. seriesType = H.seriesType;
  18856. /**
  18857. * Area series type.
  18858. * @constructor seriesTypes.area
  18859. * @extends {Series}
  18860. */
  18861. seriesType('area', 'line', {
  18862. softThreshold: false,
  18863. threshold: 0
  18864. // trackByArea: false,
  18865. // lineColor: null, // overrides color, but lets fillColor be unaltered
  18866. // fillOpacity: 0.75,
  18867. // fillColor: null
  18868. }, /** @lends seriesTypes.area.prototype */ {
  18869. singleStacks: false,
  18870. /**
  18871. * Return an array of stacked points, where null and missing points are replaced by
  18872. * dummy points in order for gaps to be drawn correctly in stacks.
  18873. */
  18874. getStackPoints: function() {
  18875. var series = this,
  18876. segment = [],
  18877. keys = [],
  18878. xAxis = this.xAxis,
  18879. yAxis = this.yAxis,
  18880. stack = yAxis.stacks[this.stackKey],
  18881. pointMap = {},
  18882. points = this.points,
  18883. seriesIndex = series.index,
  18884. yAxisSeries = yAxis.series,
  18885. seriesLength = yAxisSeries.length,
  18886. visibleSeries,
  18887. upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1,
  18888. i;
  18889. if (this.options.stacking) {
  18890. // Create a map where we can quickly look up the points by their X value.
  18891. for (i = 0; i < points.length; i++) {
  18892. pointMap[points[i].x] = points[i];
  18893. }
  18894. // Sort the keys (#1651)
  18895. H.objectEach(stack, function(stackX, x) {
  18896. if (stackX.total !== null) { // nulled after switching between grouping and not (#1651, #2336)
  18897. keys.push(x);
  18898. }
  18899. });
  18900. keys.sort(function(a, b) {
  18901. return a - b;
  18902. });
  18903. visibleSeries = map(yAxisSeries, function() {
  18904. return this.visible;
  18905. });
  18906. each(keys, function(x, idx) {
  18907. var y = 0,
  18908. stackPoint,
  18909. stackedValues;
  18910. if (pointMap[x] && !pointMap[x].isNull) {
  18911. segment.push(pointMap[x]);
  18912. // Find left and right cliff. -1 goes left, 1 goes right.
  18913. each([-1, 1], function(direction) {
  18914. var nullName = direction === 1 ? 'rightNull' : 'leftNull',
  18915. cliffName = direction === 1 ? 'rightCliff' : 'leftCliff',
  18916. cliff = 0,
  18917. otherStack = stack[keys[idx + direction]];
  18918. // If there is a stack next to this one, to the left or to the right...
  18919. if (otherStack) {
  18920. i = seriesIndex;
  18921. while (i >= 0 && i < seriesLength) { // Can go either up or down, depending on reversedStacks
  18922. stackPoint = otherStack.points[i];
  18923. if (!stackPoint) {
  18924. // If the next point in this series is missing, mark the point
  18925. // with point.leftNull or point.rightNull = true.
  18926. if (i === seriesIndex) {
  18927. pointMap[x][nullName] = true;
  18928. // If there are missing points in the next stack in any of the
  18929. // series below this one, we need to substract the missing values
  18930. // and add a hiatus to the left or right.
  18931. } else if (visibleSeries[i]) {
  18932. stackedValues = stack[x].points[i];
  18933. if (stackedValues) {
  18934. cliff -= stackedValues[1] - stackedValues[0];
  18935. }
  18936. }
  18937. }
  18938. // When reversedStacks is true, loop up, else loop down
  18939. i += upOrDown;
  18940. }
  18941. }
  18942. pointMap[x][cliffName] = cliff;
  18943. });
  18944. // There is no point for this X value in this series, so we
  18945. // insert a dummy point in order for the areas to be drawn
  18946. // correctly.
  18947. } else {
  18948. // Loop down the stack to find the series below this one that has
  18949. // a value (#1991)
  18950. i = seriesIndex;
  18951. while (i >= 0 && i < seriesLength) {
  18952. stackPoint = stack[x].points[i];
  18953. if (stackPoint) {
  18954. y = stackPoint[1];
  18955. break;
  18956. }
  18957. // When reversedStacks is true, loop up, else loop down
  18958. i += upOrDown;
  18959. }
  18960. y = yAxis.translate(y, 0, 1, 0, 1); // #6272
  18961. segment.push({
  18962. isNull: true,
  18963. plotX: xAxis.translate(x, 0, 0, 0, 1), // #6272
  18964. x: x,
  18965. plotY: y,
  18966. yBottom: y
  18967. });
  18968. }
  18969. });
  18970. }
  18971. return segment;
  18972. },
  18973. getGraphPath: function(points) {
  18974. var getGraphPath = Series.prototype.getGraphPath,
  18975. graphPath,
  18976. options = this.options,
  18977. stacking = options.stacking,
  18978. yAxis = this.yAxis,
  18979. topPath,
  18980. //topPoints = [],
  18981. bottomPath,
  18982. bottomPoints = [],
  18983. graphPoints = [],
  18984. seriesIndex = this.index,
  18985. i,
  18986. areaPath,
  18987. plotX,
  18988. stacks = yAxis.stacks[this.stackKey],
  18989. threshold = options.threshold,
  18990. translatedThreshold = yAxis.getThreshold(options.threshold),
  18991. isNull,
  18992. yBottom,
  18993. connectNulls = options.connectNulls || stacking === 'percent',
  18994. /**
  18995. * To display null points in underlying stacked series, this series graph must be
  18996. * broken, and the area also fall down to fill the gap left by the null point. #2069
  18997. */
  18998. addDummyPoints = function(i, otherI, side) {
  18999. var point = points[i],
  19000. stackedValues = stacking && stacks[point.x].points[seriesIndex],
  19001. nullVal = point[side + 'Null'] || 0,
  19002. cliffVal = point[side + 'Cliff'] || 0,
  19003. top,
  19004. bottom,
  19005. isNull = true;
  19006. if (cliffVal || nullVal) {
  19007. top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal;
  19008. bottom = stackedValues[0] + cliffVal;
  19009. isNull = !!nullVal;
  19010. } else if (!stacking && points[otherI] && points[otherI].isNull) {
  19011. top = bottom = threshold;
  19012. }
  19013. // Add to the top and bottom line of the area
  19014. if (top !== undefined) {
  19015. graphPoints.push({
  19016. plotX: plotX,
  19017. plotY: top === null ? translatedThreshold : yAxis.getThreshold(top),
  19018. isNull: isNull,
  19019. isCliff: true
  19020. });
  19021. bottomPoints.push({
  19022. plotX: plotX,
  19023. plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom),
  19024. doCurve: false // #1041, gaps in areaspline areas
  19025. });
  19026. }
  19027. };
  19028. // Find what points to use
  19029. points = points || this.points;
  19030. // Fill in missing points
  19031. if (stacking) {
  19032. points = this.getStackPoints();
  19033. }
  19034. for (i = 0; i < points.length; i++) {
  19035. isNull = points[i].isNull;
  19036. plotX = pick(points[i].rectPlotX, points[i].plotX);
  19037. yBottom = pick(points[i].yBottom, translatedThreshold);
  19038. if (!isNull || connectNulls) {
  19039. if (!connectNulls) {
  19040. addDummyPoints(i, i - 1, 'left');
  19041. }
  19042. if (!(isNull && !stacking && connectNulls)) { // Skip null point when stacking is false and connectNulls true
  19043. graphPoints.push(points[i]);
  19044. bottomPoints.push({
  19045. x: i,
  19046. plotX: plotX,
  19047. plotY: yBottom
  19048. });
  19049. }
  19050. if (!connectNulls) {
  19051. addDummyPoints(i, i + 1, 'right');
  19052. }
  19053. }
  19054. }
  19055. topPath = getGraphPath.call(this, graphPoints, true, true);
  19056. bottomPoints.reversed = true;
  19057. bottomPath = getGraphPath.call(this, bottomPoints, true, true);
  19058. if (bottomPath.length) {
  19059. bottomPath[0] = 'L';
  19060. }
  19061. areaPath = topPath.concat(bottomPath);
  19062. graphPath = getGraphPath.call(this, graphPoints, false, connectNulls); // TODO: don't set leftCliff and rightCliff when connectNulls?
  19063. areaPath.xMap = topPath.xMap;
  19064. this.areaPath = areaPath;
  19065. return graphPath;
  19066. },
  19067. /**
  19068. * Draw the graph and the underlying area. This method calls the Series base
  19069. * function and adds the area. The areaPath is calculated in the getSegmentPath
  19070. * method called from Series.prototype.drawGraph.
  19071. */
  19072. drawGraph: function() {
  19073. // Define or reset areaPath
  19074. this.areaPath = [];
  19075. // Call the base method
  19076. Series.prototype.drawGraph.apply(this);
  19077. // Define local variables
  19078. var series = this,
  19079. areaPath = this.areaPath,
  19080. options = this.options,
  19081. zones = this.zones,
  19082. props = [
  19083. [
  19084. 'area',
  19085. 'highcharts-area',
  19086. this.color,
  19087. options.fillColor
  19088. ]
  19089. ]; // area name, main color, fill color
  19090. each(zones, function(zone, i) {
  19091. props.push([
  19092. 'zone-area-' + i,
  19093. 'highcharts-area highcharts-zone-area-' + i + ' ' + zone.className,
  19094. zone.color || series.color,
  19095. zone.fillColor || options.fillColor
  19096. ]);
  19097. });
  19098. each(props, function(prop) {
  19099. var areaKey = prop[0],
  19100. area = series[areaKey];
  19101. // Create or update the area
  19102. if (area) { // update
  19103. area.endX = areaPath.xMap;
  19104. area.animate({
  19105. d: areaPath
  19106. });
  19107. } else { // create
  19108. area = series[areaKey] = series.chart.renderer.path(areaPath)
  19109. .addClass(prop[1])
  19110. .attr({
  19111. fill: pick(
  19112. prop[3],
  19113. color(prop[2]).setOpacity(pick(options.fillOpacity, 0.75)).get()
  19114. ),
  19115. zIndex: 0 // #1069
  19116. }).add(series.group);
  19117. area.isArea = true;
  19118. }
  19119. area.startX = areaPath.xMap;
  19120. area.shiftUnit = options.step ? 2 : 1;
  19121. });
  19122. },
  19123. drawLegendSymbol: LegendSymbolMixin.drawRectangle
  19124. });
  19125. }(Highcharts));
  19126. (function(H) {
  19127. /**
  19128. * (c) 2010-2017 Torstein Honsi
  19129. *
  19130. * License: www.highcharts.com/license
  19131. */
  19132. var pick = H.pick,
  19133. seriesType = H.seriesType;
  19134. /**
  19135. * Spline series type.
  19136. * @constructor seriesTypes.spline
  19137. * @extends {Series}
  19138. */
  19139. seriesType('spline', 'line', {}, /** @lends seriesTypes.spline.prototype */ {
  19140. /**
  19141. * Get the spline segment from a given point's previous neighbour to the given point
  19142. */
  19143. getPointSpline: function(points, point, i) {
  19144. var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
  19145. denom = smoothing + 1,
  19146. plotX = point.plotX,
  19147. plotY = point.plotY,
  19148. lastPoint = points[i - 1],
  19149. nextPoint = points[i + 1],
  19150. leftContX,
  19151. leftContY,
  19152. rightContX,
  19153. rightContY,
  19154. ret;
  19155. function doCurve(otherPoint) {
  19156. return otherPoint &&
  19157. !otherPoint.isNull &&
  19158. otherPoint.doCurve !== false &&
  19159. !point.isCliff; // #6387, area splines next to null
  19160. }
  19161. // Find control points
  19162. if (doCurve(lastPoint) && doCurve(nextPoint)) {
  19163. var lastX = lastPoint.plotX,
  19164. lastY = lastPoint.plotY,
  19165. nextX = nextPoint.plotX,
  19166. nextY = nextPoint.plotY,
  19167. correction = 0;
  19168. leftContX = (smoothing * plotX + lastX) / denom;
  19169. leftContY = (smoothing * plotY + lastY) / denom;
  19170. rightContX = (smoothing * plotX + nextX) / denom;
  19171. rightContY = (smoothing * plotY + nextY) / denom;
  19172. // Have the two control points make a straight line through main point
  19173. if (rightContX !== leftContX) { // #5016, division by zero
  19174. correction = ((rightContY - leftContY) * (rightContX - plotX)) /
  19175. (rightContX - leftContX) + plotY - rightContY;
  19176. }
  19177. leftContY += correction;
  19178. rightContY += correction;
  19179. // to prevent false extremes, check that control points are between
  19180. // neighbouring points' y values
  19181. if (leftContY > lastY && leftContY > plotY) {
  19182. leftContY = Math.max(lastY, plotY);
  19183. rightContY = 2 * plotY - leftContY; // mirror of left control point
  19184. } else if (leftContY < lastY && leftContY < plotY) {
  19185. leftContY = Math.min(lastY, plotY);
  19186. rightContY = 2 * plotY - leftContY;
  19187. }
  19188. if (rightContY > nextY && rightContY > plotY) {
  19189. rightContY = Math.max(nextY, plotY);
  19190. leftContY = 2 * plotY - rightContY;
  19191. } else if (rightContY < nextY && rightContY < plotY) {
  19192. rightContY = Math.min(nextY, plotY);
  19193. leftContY = 2 * plotY - rightContY;
  19194. }
  19195. // record for drawing in next point
  19196. point.rightContX = rightContX;
  19197. point.rightContY = rightContY;
  19198. }
  19199. // Visualize control points for debugging
  19200. /*
  19201. if (leftContX) {
  19202. this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2)
  19203. .attr({
  19204. stroke: 'red',
  19205. 'stroke-width': 2,
  19206. fill: 'none',
  19207. zIndex: 9
  19208. })
  19209. .add();
  19210. this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop,
  19211. 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
  19212. .attr({
  19213. stroke: 'red',
  19214. 'stroke-width': 2,
  19215. zIndex: 9
  19216. })
  19217. .add();
  19218. }
  19219. if (rightContX) {
  19220. this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2)
  19221. .attr({
  19222. stroke: 'green',
  19223. 'stroke-width': 2,
  19224. fill: 'none',
  19225. zIndex: 9
  19226. })
  19227. .add();
  19228. this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop,
  19229. 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
  19230. .attr({
  19231. stroke: 'green',
  19232. 'stroke-width': 2,
  19233. zIndex: 9
  19234. })
  19235. .add();
  19236. }
  19237. // */
  19238. ret = [
  19239. 'C',
  19240. pick(lastPoint.rightContX, lastPoint.plotX),
  19241. pick(lastPoint.rightContY, lastPoint.plotY),
  19242. pick(leftContX, plotX),
  19243. pick(leftContY, plotY),
  19244. plotX,
  19245. plotY
  19246. ];
  19247. lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
  19248. return ret;
  19249. }
  19250. });
  19251. }(Highcharts));
  19252. (function(H) {
  19253. /**
  19254. * (c) 2010-2017 Torstein Honsi
  19255. *
  19256. * License: www.highcharts.com/license
  19257. */
  19258. var areaProto = H.seriesTypes.area.prototype,
  19259. defaultPlotOptions = H.defaultPlotOptions,
  19260. LegendSymbolMixin = H.LegendSymbolMixin,
  19261. seriesType = H.seriesType;
  19262. /**
  19263. * AreaSplineSeries object
  19264. */
  19265. seriesType('areaspline', 'spline', defaultPlotOptions.area, {
  19266. getStackPoints: areaProto.getStackPoints,
  19267. getGraphPath: areaProto.getGraphPath,
  19268. setStackCliffs: areaProto.setStackCliffs,
  19269. drawGraph: areaProto.drawGraph,
  19270. drawLegendSymbol: LegendSymbolMixin.drawRectangle
  19271. });
  19272. }(Highcharts));
  19273. (function(H) {
  19274. /**
  19275. * (c) 2010-2017 Torstein Honsi
  19276. *
  19277. * License: www.highcharts.com/license
  19278. */
  19279. var animObject = H.animObject,
  19280. color = H.color,
  19281. each = H.each,
  19282. extend = H.extend,
  19283. isNumber = H.isNumber,
  19284. LegendSymbolMixin = H.LegendSymbolMixin,
  19285. merge = H.merge,
  19286. noop = H.noop,
  19287. pick = H.pick,
  19288. Series = H.Series,
  19289. seriesType = H.seriesType,
  19290. svg = H.svg;
  19291. /**
  19292. * The column series type.
  19293. *
  19294. * @constructor seriesTypes.column
  19295. * @augments Series
  19296. */
  19297. seriesType('column', 'line', {
  19298. borderRadius: 0,
  19299. //colorByPoint: undefined,
  19300. crisp: true,
  19301. groupPadding: 0.2,
  19302. //grouping: true,
  19303. marker: null, // point options are specified in the base options
  19304. pointPadding: 0.1,
  19305. //pointWidth: null,
  19306. minPointLength: 0,
  19307. cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes
  19308. pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories
  19309. states: {
  19310. hover: {
  19311. halo: false,
  19312. brightness: 0.1,
  19313. shadow: false
  19314. },
  19315. select: {
  19316. color: '#cccccc',
  19317. borderColor: '#000000',
  19318. shadow: false
  19319. }
  19320. },
  19321. dataLabels: {
  19322. align: null, // auto
  19323. verticalAlign: null, // auto
  19324. y: null
  19325. },
  19326. softThreshold: false,
  19327. startFromThreshold: true, // false doesn't work well: http://jsfiddle.net/highcharts/hz8fopan/14/
  19328. stickyTracking: false,
  19329. tooltip: {
  19330. distance: 6
  19331. },
  19332. threshold: 0,
  19333. borderColor: '#ffffff'
  19334. // borderWidth: 1
  19335. }, /** @lends seriesTypes.column.prototype */ {
  19336. cropShoulder: 0,
  19337. directTouch: true, // When tooltip is not shared, this series (and derivatives) requires direct touch/hover. KD-tree does not apply.
  19338. trackerGroups: ['group', 'dataLabelsGroup'],
  19339. negStacks: true, // use separate negative stacks, unlike area stacks where a negative
  19340. // point is substracted from previous (#1910)
  19341. /**
  19342. * Initialize the series. Extends the basic Series.init method by
  19343. * marking other series of the same type as dirty.
  19344. *
  19345. * @function #init
  19346. * @memberOf seriesTypes.column
  19347. * @returns {void}
  19348. */
  19349. init: function() {
  19350. Series.prototype.init.apply(this, arguments);
  19351. var series = this,
  19352. chart = series.chart;
  19353. // if the series is added dynamically, force redraw of other
  19354. // series affected by a new column
  19355. if (chart.hasRendered) {
  19356. each(chart.series, function(otherSeries) {
  19357. if (otherSeries.type === series.type) {
  19358. otherSeries.isDirty = true;
  19359. }
  19360. });
  19361. }
  19362. },
  19363. /**
  19364. * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding,
  19365. * pointWidth etc.
  19366. */
  19367. getColumnMetrics: function() {
  19368. var series = this,
  19369. options = series.options,
  19370. xAxis = series.xAxis,
  19371. yAxis = series.yAxis,
  19372. reversedXAxis = xAxis.reversed,
  19373. stackKey,
  19374. stackGroups = {},
  19375. columnCount = 0;
  19376. // Get the total number of column type series.
  19377. // This is called on every series. Consider moving this logic to a
  19378. // chart.orderStacks() function and call it on init, addSeries and removeSeries
  19379. if (options.grouping === false) {
  19380. columnCount = 1;
  19381. } else {
  19382. each(series.chart.series, function(otherSeries) {
  19383. var otherOptions = otherSeries.options,
  19384. otherYAxis = otherSeries.yAxis,
  19385. columnIndex;
  19386. if (
  19387. otherSeries.type === series.type &&
  19388. (
  19389. otherSeries.visible ||
  19390. !series.chart.options.chart.ignoreHiddenSeries
  19391. ) &&
  19392. yAxis.len === otherYAxis.len &&
  19393. yAxis.pos === otherYAxis.pos
  19394. ) { // #642, #2086
  19395. if (otherOptions.stacking) {
  19396. stackKey = otherSeries.stackKey;
  19397. if (stackGroups[stackKey] === undefined) {
  19398. stackGroups[stackKey] = columnCount++;
  19399. }
  19400. columnIndex = stackGroups[stackKey];
  19401. } else if (otherOptions.grouping !== false) { // #1162
  19402. columnIndex = columnCount++;
  19403. }
  19404. otherSeries.columnIndex = columnIndex;
  19405. }
  19406. });
  19407. }
  19408. var categoryWidth = Math.min(
  19409. Math.abs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || xAxis.tickInterval || 1), // #2610
  19410. xAxis.len // #1535
  19411. ),
  19412. groupPadding = categoryWidth * options.groupPadding,
  19413. groupWidth = categoryWidth - 2 * groupPadding,
  19414. pointOffsetWidth = groupWidth / (columnCount || 1),
  19415. pointWidth = Math.min(
  19416. options.maxPointWidth || xAxis.len,
  19417. pick(options.pointWidth, pointOffsetWidth * (1 - 2 * options.pointPadding))
  19418. ),
  19419. pointPadding = (pointOffsetWidth - pointWidth) / 2,
  19420. colIndex = (series.columnIndex || 0) + (reversedXAxis ? 1 : 0), // #1251, #3737
  19421. pointXOffset = pointPadding + (groupPadding + colIndex *
  19422. pointOffsetWidth - (categoryWidth / 2)) *
  19423. (reversedXAxis ? -1 : 1);
  19424. // Save it for reading in linked series (Error bars particularly)
  19425. series.columnMetrics = {
  19426. width: pointWidth,
  19427. offset: pointXOffset
  19428. };
  19429. return series.columnMetrics;
  19430. },
  19431. /**
  19432. * Make the columns crisp. The edges are rounded to the nearest full pixel.
  19433. */
  19434. crispCol: function(x, y, w, h) {
  19435. var chart = this.chart,
  19436. borderWidth = this.borderWidth,
  19437. xCrisp = -(borderWidth % 2 ? 0.5 : 0),
  19438. yCrisp = borderWidth % 2 ? 0.5 : 1,
  19439. right,
  19440. bottom,
  19441. fromTop;
  19442. if (chart.inverted && chart.renderer.isVML) {
  19443. yCrisp += 1;
  19444. }
  19445. // Horizontal. We need to first compute the exact right edge, then round it
  19446. // and compute the width from there.
  19447. if (this.options.crisp) {
  19448. right = Math.round(x + w) + xCrisp;
  19449. x = Math.round(x) + xCrisp;
  19450. w = right - x;
  19451. }
  19452. // Vertical
  19453. bottom = Math.round(y + h) + yCrisp;
  19454. fromTop = Math.abs(y) <= 0.5 && bottom > 0.5; // #4504, #4656
  19455. y = Math.round(y) + yCrisp;
  19456. h = bottom - y;
  19457. // Top edges are exceptions
  19458. if (fromTop && h) { // #5146
  19459. y -= 1;
  19460. h += 1;
  19461. }
  19462. return {
  19463. x: x,
  19464. y: y,
  19465. width: w,
  19466. height: h
  19467. };
  19468. },
  19469. /**
  19470. * Translate each point to the plot area coordinate system and find shape positions
  19471. */
  19472. translate: function() {
  19473. var series = this,
  19474. chart = series.chart,
  19475. options = series.options,
  19476. dense = series.dense = series.closestPointRange * series.xAxis.transA < 2,
  19477. borderWidth = series.borderWidth = pick(
  19478. options.borderWidth,
  19479. dense ? 0 : 1 // #3635
  19480. ),
  19481. yAxis = series.yAxis,
  19482. threshold = options.threshold,
  19483. translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),
  19484. minPointLength = pick(options.minPointLength, 5),
  19485. metrics = series.getColumnMetrics(),
  19486. pointWidth = metrics.width,
  19487. seriesBarW = series.barW = Math.max(pointWidth, 1 + 2 * borderWidth), // postprocessed for border width
  19488. pointXOffset = series.pointXOffset = metrics.offset;
  19489. if (chart.inverted) {
  19490. translatedThreshold -= 0.5; // #3355
  19491. }
  19492. // When the pointPadding is 0, we want the columns to be packed tightly, so we allow individual
  19493. // columns to have individual sizes. When pointPadding is greater, we strive for equal-width
  19494. // columns (#2694).
  19495. if (options.pointPadding) {
  19496. seriesBarW = Math.ceil(seriesBarW);
  19497. }
  19498. Series.prototype.translate.apply(series);
  19499. // Record the new values
  19500. each(series.points, function(point) {
  19501. var yBottom = pick(point.yBottom, translatedThreshold),
  19502. safeDistance = 999 + Math.abs(yBottom),
  19503. plotY = Math.min(Math.max(-safeDistance, point.plotY), yAxis.len + safeDistance), // Don't draw too far outside plot area (#1303, #2241, #4264)
  19504. barX = point.plotX + pointXOffset,
  19505. barW = seriesBarW,
  19506. barY = Math.min(plotY, yBottom),
  19507. up,
  19508. barH = Math.max(plotY, yBottom) - barY;
  19509. // Handle options.minPointLength
  19510. if (Math.abs(barH) < minPointLength) {
  19511. if (minPointLength) {
  19512. barH = minPointLength;
  19513. up = (!yAxis.reversed && !point.negative) || (yAxis.reversed && point.negative);
  19514. barY = Math.abs(barY - translatedThreshold) > minPointLength ? // stacked
  19515. yBottom - minPointLength : // keep position
  19516. translatedThreshold - (up ? minPointLength : 0); // #1485, #4051
  19517. }
  19518. }
  19519. // Cache for access in polar
  19520. point.barX = barX;
  19521. point.pointWidth = pointWidth;
  19522. // Fix the tooltip on center of grouped columns (#1216, #424, #3648)
  19523. point.tooltipPos = chart.inverted ? [yAxis.len + yAxis.pos - chart.plotLeft - plotY, series.xAxis.len - barX - barW / 2, barH] : [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH];
  19524. // Register shape type and arguments to be used in drawPoints
  19525. point.shapeType = 'rect';
  19526. point.shapeArgs = series.crispCol.apply(
  19527. series,
  19528. point.isNull ?
  19529. // #3169, drilldown from null must have a position to work from
  19530. // #6585, dataLabel should be placed on xAxis, not floating in the middle of the chart
  19531. [barX, translatedThreshold, barW, 0] : [barX, barY, barW, barH]
  19532. );
  19533. });
  19534. },
  19535. getSymbol: noop,
  19536. /**
  19537. * Use a solid rectangle like the area series types
  19538. */
  19539. drawLegendSymbol: LegendSymbolMixin.drawRectangle,
  19540. /**
  19541. * Columns have no graph
  19542. */
  19543. drawGraph: function() {
  19544. this.group[this.dense ? 'addClass' : 'removeClass']('highcharts-dense-data');
  19545. },
  19546. /**
  19547. * Get presentational attributes
  19548. */
  19549. pointAttribs: function(point, state) {
  19550. var options = this.options,
  19551. stateOptions,
  19552. ret,
  19553. p2o = this.pointAttrToOptions || {},
  19554. strokeOption = p2o.stroke || 'borderColor',
  19555. strokeWidthOption = p2o['stroke-width'] || 'borderWidth',
  19556. fill = (point && point.color) || this.color,
  19557. stroke = point[strokeOption] || options[strokeOption] ||
  19558. this.color || fill, // set to fill when borderColor null
  19559. strokeWidth = point[strokeWidthOption] ||
  19560. options[strokeWidthOption] || this[strokeWidthOption] || 0,
  19561. dashstyle = options.dashStyle,
  19562. zone,
  19563. brightness;
  19564. // Handle zone colors
  19565. if (point && this.zones.length) {
  19566. zone = point.getZone();
  19567. fill = point.options.color || (zone && zone.color) || this.color; // When zones are present, don't use point.color (#4267). Changed order (#6527)
  19568. }
  19569. // Select or hover states
  19570. if (state) {
  19571. stateOptions = merge(
  19572. options.states[state],
  19573. point.options.states && point.options.states[state] || {} // #6401
  19574. );
  19575. brightness = stateOptions.brightness;
  19576. fill = stateOptions.color ||
  19577. (brightness !== undefined && color(fill).brighten(stateOptions.brightness).get()) ||
  19578. fill;
  19579. stroke = stateOptions[strokeOption] || stroke;
  19580. strokeWidth = stateOptions[strokeWidthOption] || strokeWidth;
  19581. dashstyle = stateOptions.dashStyle || dashstyle;
  19582. }
  19583. ret = {
  19584. 'fill': fill,
  19585. 'stroke': stroke,
  19586. 'stroke-width': strokeWidth
  19587. };
  19588. if (options.borderRadius) {
  19589. ret.r = options.borderRadius;
  19590. }
  19591. if (dashstyle) {
  19592. ret.dashstyle = dashstyle;
  19593. }
  19594. return ret;
  19595. },
  19596. /**
  19597. * Draw the columns. For bars, the series.group is rotated, so the same coordinates
  19598. * apply for columns and bars. This method is inherited by scatter series.
  19599. *
  19600. */
  19601. drawPoints: function() {
  19602. var series = this,
  19603. chart = this.chart,
  19604. options = series.options,
  19605. renderer = chart.renderer,
  19606. animationLimit = options.animationLimit || 250,
  19607. shapeArgs;
  19608. // draw the columns
  19609. each(series.points, function(point) {
  19610. var plotY = point.plotY,
  19611. graphic = point.graphic;
  19612. if (isNumber(plotY) && point.y !== null) {
  19613. shapeArgs = point.shapeArgs;
  19614. if (graphic) { // update
  19615. graphic[chart.pointCount < animationLimit ? 'animate' : 'attr'](
  19616. merge(shapeArgs)
  19617. );
  19618. } else {
  19619. point.graphic = graphic = renderer[point.shapeType](shapeArgs)
  19620. .add(point.group || series.group);
  19621. }
  19622. // Presentational
  19623. graphic
  19624. .attr(series.pointAttribs(point, point.selected && 'select'))
  19625. .shadow(options.shadow, null, options.stacking && !options.borderRadius);
  19626. graphic.addClass(point.getClassName(), true);
  19627. } else if (graphic) {
  19628. point.graphic = graphic.destroy(); // #1269
  19629. }
  19630. });
  19631. },
  19632. /**
  19633. * Animate the column heights one by one from zero
  19634. * @param {Boolean} init Whether to initialize the animation or run it
  19635. */
  19636. animate: function(init) {
  19637. var series = this,
  19638. yAxis = this.yAxis,
  19639. options = series.options,
  19640. inverted = this.chart.inverted,
  19641. attr = {},
  19642. translatedThreshold;
  19643. if (svg) { // VML is too slow anyway
  19644. if (init) {
  19645. attr.scaleY = 0.001;
  19646. translatedThreshold = Math.min(yAxis.pos + yAxis.len, Math.max(yAxis.pos, yAxis.toPixels(options.threshold)));
  19647. if (inverted) {
  19648. attr.translateX = translatedThreshold - yAxis.len;
  19649. } else {
  19650. attr.translateY = translatedThreshold;
  19651. }
  19652. series.group.attr(attr);
  19653. } else { // run the animation
  19654. attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos;
  19655. series.group.animate(attr, extend(animObject(series.options.animation), {
  19656. // Do the scale synchronously to ensure smooth updating (#5030)
  19657. step: function(val, fx) {
  19658. series.group.attr({
  19659. scaleY: Math.max(0.001, fx.pos) // #5250
  19660. });
  19661. }
  19662. }));
  19663. // delete this function to allow it only once
  19664. series.animate = null;
  19665. }
  19666. }
  19667. },
  19668. /**
  19669. * Remove this series from the chart
  19670. */
  19671. remove: function() {
  19672. var series = this,
  19673. chart = series.chart;
  19674. // column and bar series affects other series of the same type
  19675. // as they are either stacked or grouped
  19676. if (chart.hasRendered) {
  19677. each(chart.series, function(otherSeries) {
  19678. if (otherSeries.type === series.type) {
  19679. otherSeries.isDirty = true;
  19680. }
  19681. });
  19682. }
  19683. Series.prototype.remove.apply(series, arguments);
  19684. }
  19685. });
  19686. }(Highcharts));
  19687. (function(H) {
  19688. /**
  19689. * (c) 2010-2017 Torstein Honsi
  19690. *
  19691. * License: www.highcharts.com/license
  19692. */
  19693. var seriesType = H.seriesType;
  19694. /**
  19695. * The Bar series class
  19696. */
  19697. seriesType('bar', 'column', null, {
  19698. inverted: true
  19699. });
  19700. }(Highcharts));
  19701. (function(H) {
  19702. /**
  19703. * (c) 2010-2017 Torstein Honsi
  19704. *
  19705. * License: www.highcharts.com/license
  19706. */
  19707. var Series = H.Series,
  19708. seriesType = H.seriesType;
  19709. /**
  19710. * The scatter series type
  19711. */
  19712. seriesType('scatter', 'line', {
  19713. lineWidth: 0,
  19714. findNearestPointBy: 'xy',
  19715. marker: {
  19716. enabled: true // Overrides auto-enabling in line series (#3647)
  19717. },
  19718. tooltip: {
  19719. headerFormat: '<span style="color:{point.color}">\u25CF</span> ' +
  19720. '<span style="font-size: 0.85em"> {series.name}</span><br/>',
  19721. pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
  19722. }
  19723. // Prototype members
  19724. }, {
  19725. sorted: false,
  19726. requireSorting: false,
  19727. noSharedTooltip: true,
  19728. trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
  19729. takeOrdinalPosition: false, // #2342
  19730. drawGraph: function() {
  19731. if (this.options.lineWidth) {
  19732. Series.prototype.drawGraph.call(this);
  19733. }
  19734. }
  19735. });
  19736. }(Highcharts));
  19737. (function(H) {
  19738. /**
  19739. * (c) 2010-2017 Torstein Honsi
  19740. *
  19741. * License: www.highcharts.com/license
  19742. */
  19743. var pick = H.pick,
  19744. relativeLength = H.relativeLength;
  19745. H.CenteredSeriesMixin = {
  19746. /**
  19747. * Get the center of the pie based on the size and center options relative to the
  19748. * plot area. Borrowed by the polar and gauge series types.
  19749. */
  19750. getCenter: function() {
  19751. var options = this.options,
  19752. chart = this.chart,
  19753. slicingRoom = 2 * (options.slicedOffset || 0),
  19754. handleSlicingRoom,
  19755. plotWidth = chart.plotWidth - 2 * slicingRoom,
  19756. plotHeight = chart.plotHeight - 2 * slicingRoom,
  19757. centerOption = options.center,
  19758. positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
  19759. smallestSize = Math.min(plotWidth, plotHeight),
  19760. i,
  19761. value;
  19762. for (i = 0; i < 4; ++i) {
  19763. value = positions[i];
  19764. handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value));
  19765. // i == 0: centerX, relative to width
  19766. // i == 1: centerY, relative to height
  19767. // i == 2: size, relative to smallestSize
  19768. // i == 3: innerSize, relative to size
  19769. positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) +
  19770. (handleSlicingRoom ? slicingRoom : 0);
  19771. }
  19772. // innerSize cannot be larger than size (#3632)
  19773. if (positions[3] > positions[2]) {
  19774. positions[3] = positions[2];
  19775. }
  19776. return positions;
  19777. }
  19778. };
  19779. }(Highcharts));
  19780. (function(H) {
  19781. /**
  19782. * (c) 2010-2017 Torstein Honsi
  19783. *
  19784. * License: www.highcharts.com/license
  19785. */
  19786. var addEvent = H.addEvent,
  19787. CenteredSeriesMixin = H.CenteredSeriesMixin,
  19788. defined = H.defined,
  19789. each = H.each,
  19790. extend = H.extend,
  19791. inArray = H.inArray,
  19792. LegendSymbolMixin = H.LegendSymbolMixin,
  19793. noop = H.noop,
  19794. pick = H.pick,
  19795. Point = H.Point,
  19796. Series = H.Series,
  19797. seriesType = H.seriesType,
  19798. seriesTypes = H.seriesTypes,
  19799. setAnimation = H.setAnimation;
  19800. /**
  19801. * The pie series type.
  19802. *
  19803. * @constructor seriesTypes.pie
  19804. * @augments Series
  19805. */
  19806. seriesType('pie', 'line', {
  19807. center: [null, null],
  19808. clip: false,
  19809. colorByPoint: true, // always true for pies
  19810. dataLabels: {
  19811. // align: null,
  19812. // connectorWidth: 1,
  19813. // connectorColor: point.color,
  19814. // connectorPadding: 5,
  19815. distance: 30,
  19816. enabled: true,
  19817. formatter: function() { // #2945
  19818. return this.point.isNull ? undefined : this.point.name;
  19819. },
  19820. // softConnector: true,
  19821. x: 0
  19822. // y: 0
  19823. },
  19824. ignoreHiddenPoint: true,
  19825. //innerSize: 0,
  19826. legendType: 'point',
  19827. marker: null, // point options are specified in the base options
  19828. size: null,
  19829. showInLegend: false,
  19830. slicedOffset: 10,
  19831. stickyTracking: false,
  19832. tooltip: {
  19833. followPointer: true
  19834. },
  19835. borderColor: '#ffffff',
  19836. borderWidth: 1,
  19837. states: {
  19838. hover: {
  19839. brightness: 0.1,
  19840. shadow: false
  19841. }
  19842. }
  19843. }, /** @lends seriesTypes.pie.prototype */ {
  19844. isCartesian: false,
  19845. requireSorting: false,
  19846. directTouch: true,
  19847. noSharedTooltip: true,
  19848. trackerGroups: ['group', 'dataLabelsGroup'],
  19849. axisTypes: [],
  19850. pointAttribs: seriesTypes.column.prototype.pointAttribs,
  19851. /**
  19852. * Animate the pies in
  19853. */
  19854. animate: function(init) {
  19855. var series = this,
  19856. points = series.points,
  19857. startAngleRad = series.startAngleRad;
  19858. if (!init) {
  19859. each(points, function(point) {
  19860. var graphic = point.graphic,
  19861. args = point.shapeArgs;
  19862. if (graphic) {
  19863. // start values
  19864. graphic.attr({
  19865. r: point.startR || (series.center[3] / 2), // animate from inner radius (#779)
  19866. start: startAngleRad,
  19867. end: startAngleRad
  19868. });
  19869. // animate
  19870. graphic.animate({
  19871. r: args.r,
  19872. start: args.start,
  19873. end: args.end
  19874. }, series.options.animation);
  19875. }
  19876. });
  19877. // delete this function to allow it only once
  19878. series.animate = null;
  19879. }
  19880. },
  19881. /**
  19882. * Recompute total chart sum and update percentages of points.
  19883. */
  19884. updateTotals: function() {
  19885. var i,
  19886. total = 0,
  19887. points = this.points,
  19888. len = points.length,
  19889. point,
  19890. ignoreHiddenPoint = this.options.ignoreHiddenPoint;
  19891. // Get the total sum
  19892. for (i = 0; i < len; i++) {
  19893. point = points[i];
  19894. total += (ignoreHiddenPoint && !point.visible) ?
  19895. 0 :
  19896. point.isNull ? 0 : point.y;
  19897. }
  19898. this.total = total;
  19899. // Set each point's properties
  19900. for (i = 0; i < len; i++) {
  19901. point = points[i];
  19902. point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0;
  19903. point.total = total;
  19904. }
  19905. },
  19906. /**
  19907. * Extend the generatePoints method by adding total and percentage properties to each point
  19908. */
  19909. generatePoints: function() {
  19910. Series.prototype.generatePoints.call(this);
  19911. this.updateTotals();
  19912. },
  19913. /**
  19914. * Do translation for pie slices
  19915. */
  19916. translate: function(positions) {
  19917. this.generatePoints();
  19918. var series = this,
  19919. cumulative = 0,
  19920. precision = 1000, // issue #172
  19921. options = series.options,
  19922. slicedOffset = options.slicedOffset,
  19923. connectorOffset = slicedOffset + (options.borderWidth || 0),
  19924. finalConnectorOffset,
  19925. start,
  19926. end,
  19927. angle,
  19928. startAngle = options.startAngle || 0,
  19929. startAngleRad = series.startAngleRad = Math.PI / 180 * (startAngle - 90),
  19930. endAngleRad = series.endAngleRad = Math.PI / 180 * ((pick(options.endAngle, startAngle + 360)) - 90),
  19931. circ = endAngleRad - startAngleRad, //2 * Math.PI,
  19932. points = series.points,
  19933. radiusX, // the x component of the radius vector for a given point
  19934. radiusY,
  19935. labelDistance = options.dataLabels.distance,
  19936. ignoreHiddenPoint = options.ignoreHiddenPoint,
  19937. i,
  19938. len = points.length,
  19939. point;
  19940. // Get positions - either an integer or a percentage string must be given.
  19941. // If positions are passed as a parameter, we're in a recursive loop for adjusting
  19942. // space for data labels.
  19943. if (!positions) {
  19944. series.center = positions = series.getCenter();
  19945. }
  19946. // Utility for getting the x value from a given y, used for anticollision
  19947. // logic in data labels.
  19948. // Added point for using specific points' label distance.
  19949. series.getX = function(y, left, point) {
  19950. angle = Math.asin(Math.min((y - positions[1]) / (positions[2] / 2 + point.labelDistance), 1));
  19951. return positions[0] +
  19952. (left ? -1 : 1) *
  19953. (Math.cos(angle) * (positions[2] / 2 + point.labelDistance));
  19954. };
  19955. // Calculate the geometry for each point
  19956. for (i = 0; i < len; i++) {
  19957. point = points[i];
  19958. // Used for distance calculation for specific point.
  19959. point.labelDistance = pick(
  19960. point.options.dataLabels && point.options.dataLabels.distance,
  19961. labelDistance
  19962. );
  19963. // Saved for later dataLabels distance calculation.
  19964. series.maxLabelDistance = Math.max(series.maxLabelDistance || 0, point.labelDistance);
  19965. // set start and end angle
  19966. start = startAngleRad + (cumulative * circ);
  19967. if (!ignoreHiddenPoint || point.visible) {
  19968. cumulative += point.percentage / 100;
  19969. }
  19970. end = startAngleRad + (cumulative * circ);
  19971. // set the shape
  19972. point.shapeType = 'arc';
  19973. point.shapeArgs = {
  19974. x: positions[0],
  19975. y: positions[1],
  19976. r: positions[2] / 2,
  19977. innerR: positions[3] / 2,
  19978. start: Math.round(start * precision) / precision,
  19979. end: Math.round(end * precision) / precision
  19980. };
  19981. // The angle must stay within -90 and 270 (#2645)
  19982. angle = (end + start) / 2;
  19983. if (angle > 1.5 * Math.PI) {
  19984. angle -= 2 * Math.PI;
  19985. } else if (angle < -Math.PI / 2) {
  19986. angle += 2 * Math.PI;
  19987. }
  19988. // Center for the sliced out slice
  19989. point.slicedTranslation = {
  19990. translateX: Math.round(Math.cos(angle) * slicedOffset),
  19991. translateY: Math.round(Math.sin(angle) * slicedOffset)
  19992. };
  19993. // set the anchor point for tooltips
  19994. radiusX = Math.cos(angle) * positions[2] / 2;
  19995. radiusY = Math.sin(angle) * positions[2] / 2;
  19996. point.tooltipPos = [
  19997. positions[0] + radiusX * 0.7,
  19998. positions[1] + radiusY * 0.7
  19999. ];
  20000. point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ? 1 : 0;
  20001. point.angle = angle;
  20002. // Set the anchor point for data labels. Use point.labelDistance
  20003. // instead of labelDistance // #1174
  20004. // finalConnectorOffset - not override connectorOffset value.
  20005. finalConnectorOffset = Math.min(connectorOffset, point.labelDistance / 5); // #1678
  20006. point.labelPos = [
  20007. positions[0] + radiusX + Math.cos(angle) * point.labelDistance, // first break of connector
  20008. positions[1] + radiusY + Math.sin(angle) * point.labelDistance, // a/a
  20009. positions[0] + radiusX + Math.cos(angle) * finalConnectorOffset, // second break, right outside pie
  20010. positions[1] + radiusY + Math.sin(angle) * finalConnectorOffset, // a/a
  20011. positions[0] + radiusX, // landing point for connector
  20012. positions[1] + radiusY, // a/a
  20013. point.labelDistance < 0 ? // alignment
  20014. 'center' :
  20015. point.half ? 'right' : 'left', // alignment
  20016. angle // center angle
  20017. ];
  20018. }
  20019. },
  20020. drawGraph: null,
  20021. /**
  20022. * Draw the data points
  20023. */
  20024. drawPoints: function() {
  20025. var series = this,
  20026. chart = series.chart,
  20027. renderer = chart.renderer,
  20028. groupTranslation,
  20029. //center,
  20030. graphic,
  20031. //group,
  20032. pointAttr,
  20033. shapeArgs;
  20034. var shadow = series.options.shadow;
  20035. if (shadow && !series.shadowGroup) {
  20036. series.shadowGroup = renderer.g('shadow')
  20037. .add(series.group);
  20038. }
  20039. // draw the slices
  20040. each(series.points, function(point) {
  20041. if (!point.isNull) {
  20042. graphic = point.graphic;
  20043. shapeArgs = point.shapeArgs;
  20044. // If the point is sliced, use special translation, else use
  20045. // plot area traslation
  20046. groupTranslation = point.getTranslate();
  20047. // Put the shadow behind all points
  20048. var shadowGroup = point.shadowGroup;
  20049. if (shadow && !shadowGroup) {
  20050. shadowGroup = point.shadowGroup = renderer.g('shadow')
  20051. .add(series.shadowGroup);
  20052. }
  20053. if (shadowGroup) {
  20054. shadowGroup.attr(groupTranslation);
  20055. }
  20056. pointAttr = series.pointAttribs(point, point.selected && 'select');
  20057. // Draw the slice
  20058. if (graphic) {
  20059. graphic
  20060. .setRadialReference(series.center)
  20061. .attr(pointAttr)
  20062. .animate(extend(shapeArgs, groupTranslation));
  20063. } else {
  20064. point.graphic = graphic = renderer[point.shapeType](shapeArgs)
  20065. .setRadialReference(series.center)
  20066. .attr(groupTranslation)
  20067. .add(series.group);
  20068. if (!point.visible) {
  20069. graphic.attr({
  20070. visibility: 'hidden'
  20071. });
  20072. }
  20073. graphic
  20074. .attr(pointAttr)
  20075. .attr({
  20076. 'stroke-linejoin': 'round'
  20077. })
  20078. .shadow(shadow, shadowGroup);
  20079. }
  20080. graphic.addClass(point.getClassName());
  20081. }
  20082. });
  20083. },
  20084. searchPoint: noop,
  20085. /**
  20086. * Utility for sorting data labels
  20087. */
  20088. sortByAngle: function(points, sign) {
  20089. points.sort(function(a, b) {
  20090. return a.angle !== undefined && (b.angle - a.angle) * sign;
  20091. });
  20092. },
  20093. /**
  20094. * Use a simple symbol from LegendSymbolMixin
  20095. */
  20096. drawLegendSymbol: LegendSymbolMixin.drawRectangle,
  20097. /**
  20098. * Use the getCenter method from drawLegendSymbol
  20099. */
  20100. getCenter: CenteredSeriesMixin.getCenter,
  20101. /**
  20102. * Pies don't have point marker symbols
  20103. */
  20104. getSymbol: noop
  20105. /**
  20106. * @constructor seriesTypes.pie.prototype.pointClass
  20107. * @extends {Point}
  20108. */
  20109. }, /** @lends seriesTypes.pie.prototype.pointClass.prototype */ {
  20110. /**
  20111. * Initiate the pie slice
  20112. */
  20113. init: function() {
  20114. Point.prototype.init.apply(this, arguments);
  20115. var point = this,
  20116. toggleSlice;
  20117. point.name = pick(point.name, 'Slice');
  20118. // add event listener for select
  20119. toggleSlice = function(e) {
  20120. point.slice(e.type === 'select');
  20121. };
  20122. addEvent(point, 'select', toggleSlice);
  20123. addEvent(point, 'unselect', toggleSlice);
  20124. return point;
  20125. },
  20126. /**
  20127. * Negative points are not valid (#1530, #3623, #5322)
  20128. */
  20129. isValid: function() {
  20130. return H.isNumber(this.y, true) && this.y >= 0;
  20131. },
  20132. /**
  20133. * Toggle the visibility of the pie slice
  20134. * @param {Boolean} vis Whether to show the slice or not. If undefined, the
  20135. * visibility is toggled
  20136. */
  20137. setVisible: function(vis, redraw) {
  20138. var point = this,
  20139. series = point.series,
  20140. chart = series.chart,
  20141. ignoreHiddenPoint = series.options.ignoreHiddenPoint;
  20142. redraw = pick(redraw, ignoreHiddenPoint);
  20143. if (vis !== point.visible) {
  20144. // If called without an argument, toggle visibility
  20145. point.visible = point.options.visible = vis = vis === undefined ? !point.visible : vis;
  20146. series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
  20147. // Show and hide associated elements. This is performed regardless of redraw or not,
  20148. // because chart.redraw only handles full series.
  20149. each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function(key) {
  20150. if (point[key]) {
  20151. point[key][vis ? 'show' : 'hide'](true);
  20152. }
  20153. });
  20154. if (point.legendItem) {
  20155. chart.legend.colorizeItem(point, vis);
  20156. }
  20157. // #4170, hide halo after hiding point
  20158. if (!vis && point.state === 'hover') {
  20159. point.setState('');
  20160. }
  20161. // Handle ignore hidden slices
  20162. if (ignoreHiddenPoint) {
  20163. series.isDirty = true;
  20164. }
  20165. if (redraw) {
  20166. chart.redraw();
  20167. }
  20168. }
  20169. },
  20170. /**
  20171. * Set or toggle whether the slice is cut out from the pie
  20172. * @param {Boolean} sliced When undefined, the slice state is toggled
  20173. * @param {Boolean} redraw Whether to redraw the chart. True by default.
  20174. */
  20175. slice: function(sliced, redraw, animation) {
  20176. var point = this,
  20177. series = point.series,
  20178. chart = series.chart;
  20179. setAnimation(animation, chart);
  20180. // redraw is true by default
  20181. redraw = pick(redraw, true);
  20182. // if called without an argument, toggle
  20183. point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;
  20184. series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
  20185. point.graphic.animate(this.getTranslate());
  20186. if (point.shadowGroup) {
  20187. point.shadowGroup.animate(this.getTranslate());
  20188. }
  20189. },
  20190. getTranslate: function() {
  20191. return this.sliced ? this.slicedTranslation : {
  20192. translateX: 0,
  20193. translateY: 0
  20194. };
  20195. },
  20196. haloPath: function(size) {
  20197. var shapeArgs = this.shapeArgs;
  20198. return this.sliced || !this.visible ? [] :
  20199. this.series.chart.renderer.symbols.arc(
  20200. shapeArgs.x,
  20201. shapeArgs.y,
  20202. shapeArgs.r + size,
  20203. shapeArgs.r + size, {
  20204. innerR: this.shapeArgs.r,
  20205. start: shapeArgs.start,
  20206. end: shapeArgs.end
  20207. }
  20208. );
  20209. }
  20210. });
  20211. }(Highcharts));
  20212. (function(H) {
  20213. /**
  20214. * (c) 2010-2017 Torstein Honsi
  20215. *
  20216. * License: www.highcharts.com/license
  20217. */
  20218. var addEvent = H.addEvent,
  20219. arrayMax = H.arrayMax,
  20220. defined = H.defined,
  20221. each = H.each,
  20222. extend = H.extend,
  20223. format = H.format,
  20224. map = H.map,
  20225. merge = H.merge,
  20226. noop = H.noop,
  20227. pick = H.pick,
  20228. relativeLength = H.relativeLength,
  20229. Series = H.Series,
  20230. seriesTypes = H.seriesTypes,
  20231. stableSort = H.stableSort;
  20232. /**
  20233. * Generatl distribution algorithm for distributing labels of differing size along a
  20234. * confined length in two dimensions. The algorithm takes an array of objects containing
  20235. * a size, a target and a rank. It will place the labels as close as possible to their
  20236. * targets, skipping the lowest ranked labels if necessary.
  20237. */
  20238. H.distribute = function(boxes, len) {
  20239. var i,
  20240. overlapping = true,
  20241. origBoxes = boxes, // Original array will be altered with added .pos
  20242. restBoxes = [], // The outranked overshoot
  20243. box,
  20244. target,
  20245. total = 0;
  20246. function sortByTarget(a, b) {
  20247. return a.target - b.target;
  20248. }
  20249. // If the total size exceeds the len, remove those boxes with the lowest rank
  20250. i = boxes.length;
  20251. while (i--) {
  20252. total += boxes[i].size;
  20253. }
  20254. // Sort by rank, then slice away overshoot
  20255. if (total > len) {
  20256. stableSort(boxes, function(a, b) {
  20257. return (b.rank || 0) - (a.rank || 0);
  20258. });
  20259. i = 0;
  20260. total = 0;
  20261. while (total <= len) {
  20262. total += boxes[i].size;
  20263. i++;
  20264. }
  20265. restBoxes = boxes.splice(i - 1, boxes.length);
  20266. }
  20267. // Order by target
  20268. stableSort(boxes, sortByTarget);
  20269. // So far we have been mutating the original array. Now
  20270. // create a copy with target arrays
  20271. boxes = map(boxes, function(box) {
  20272. return {
  20273. size: box.size,
  20274. targets: [box.target]
  20275. };
  20276. });
  20277. while (overlapping) {
  20278. // Initial positions: target centered in box
  20279. i = boxes.length;
  20280. while (i--) {
  20281. box = boxes[i];
  20282. // Composite box, average of targets
  20283. target = (Math.min.apply(0, box.targets) + Math.max.apply(0, box.targets)) / 2;
  20284. box.pos = Math.min(Math.max(0, target - box.size / 2), len - box.size);
  20285. }
  20286. // Detect overlap and join boxes
  20287. i = boxes.length;
  20288. overlapping = false;
  20289. while (i--) {
  20290. if (i > 0 && boxes[i - 1].pos + boxes[i - 1].size > boxes[i].pos) { // Overlap
  20291. boxes[i - 1].size += boxes[i].size; // Add this size to the previous box
  20292. boxes[i - 1].targets = boxes[i - 1].targets.concat(boxes[i].targets);
  20293. // Overlapping right, push left
  20294. if (boxes[i - 1].pos + boxes[i - 1].size > len) {
  20295. boxes[i - 1].pos = len - boxes[i - 1].size;
  20296. }
  20297. boxes.splice(i, 1); // Remove this item
  20298. overlapping = true;
  20299. }
  20300. }
  20301. }
  20302. // Now the composite boxes are placed, we need to put the original boxes within them
  20303. i = 0;
  20304. each(boxes, function(box) {
  20305. var posInCompositeBox = 0;
  20306. each(box.targets, function() {
  20307. origBoxes[i].pos = box.pos + posInCompositeBox;
  20308. posInCompositeBox += origBoxes[i].size;
  20309. i++;
  20310. });
  20311. });
  20312. // Add the rest (hidden) boxes and sort by target
  20313. origBoxes.push.apply(origBoxes, restBoxes);
  20314. stableSort(origBoxes, sortByTarget);
  20315. };
  20316. /**
  20317. * Draw the data labels
  20318. */
  20319. Series.prototype.drawDataLabels = function() {
  20320. var series = this,
  20321. seriesOptions = series.options,
  20322. options = seriesOptions.dataLabels,
  20323. points = series.points,
  20324. pointOptions,
  20325. generalOptions,
  20326. hasRendered = series.hasRendered || 0,
  20327. str,
  20328. dataLabelsGroup,
  20329. defer = pick(options.defer, !!seriesOptions.animation),
  20330. renderer = series.chart.renderer;
  20331. if (options.enabled || series._hasPointLabels) {
  20332. // Process default alignment of data labels for columns
  20333. if (series.dlProcessOptions) {
  20334. series.dlProcessOptions(options);
  20335. }
  20336. // Create a separate group for the data labels to avoid rotation
  20337. dataLabelsGroup = series.plotGroup(
  20338. 'dataLabelsGroup',
  20339. 'data-labels',
  20340. defer && !hasRendered ? 'hidden' : 'visible', // #5133
  20341. options.zIndex || 6
  20342. );
  20343. if (defer) {
  20344. dataLabelsGroup.attr({
  20345. opacity: +hasRendered
  20346. }); // #3300
  20347. if (!hasRendered) {
  20348. addEvent(series, 'afterAnimate', function() {
  20349. if (series.visible) { // #2597, #3023, #3024
  20350. dataLabelsGroup.show(true);
  20351. }
  20352. dataLabelsGroup[seriesOptions.animation ? 'animate' : 'attr']({
  20353. opacity: 1
  20354. }, {
  20355. duration: 200
  20356. });
  20357. });
  20358. }
  20359. }
  20360. // Make the labels for each point
  20361. generalOptions = options;
  20362. each(points, function(point) {
  20363. var enabled,
  20364. dataLabel = point.dataLabel,
  20365. labelConfig,
  20366. attr,
  20367. rotation,
  20368. connector = point.connector,
  20369. isNew = !dataLabel,
  20370. style;
  20371. // Determine if each data label is enabled
  20372. // @note dataLabelAttribs (like pointAttribs) would eradicate
  20373. // the need for dlOptions, and simplify the section below.
  20374. pointOptions = point.dlOptions || (point.options && point.options.dataLabels); // dlOptions is used in treemaps
  20375. enabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled) && point.y !== null; // #2282, #4641
  20376. if (enabled) {
  20377. // Create individual options structure that can be extended without
  20378. // affecting others
  20379. options = merge(generalOptions, pointOptions);
  20380. labelConfig = point.getLabelConfig();
  20381. str = options.format ?
  20382. format(options.format, labelConfig) :
  20383. options.formatter.call(labelConfig, options);
  20384. style = options.style;
  20385. rotation = options.rotation;
  20386. // Determine the color
  20387. style.color = pick(options.color, style.color, series.color, '#000000');
  20388. // Get automated contrast color
  20389. if (style.color === 'contrast') {
  20390. point.contrastColor = renderer.getContrast(point.color || series.color);
  20391. style.color = options.inside || pick(point.labelDistance, options.distance) < 0 ||
  20392. !!seriesOptions.stacking ? point.contrastColor : '#000000';
  20393. }
  20394. if (seriesOptions.cursor) {
  20395. style.cursor = seriesOptions.cursor;
  20396. }
  20397. attr = {
  20398. //align: align,
  20399. fill: options.backgroundColor,
  20400. stroke: options.borderColor,
  20401. 'stroke-width': options.borderWidth,
  20402. r: options.borderRadius || 0,
  20403. rotation: rotation,
  20404. padding: options.padding,
  20405. zIndex: 1
  20406. };
  20407. // Remove unused attributes (#947)
  20408. H.objectEach(attr, function(val, name) {
  20409. if (val === undefined) {
  20410. delete attr[name];
  20411. }
  20412. });
  20413. }
  20414. // If the point is outside the plot area, destroy it. #678, #820
  20415. if (dataLabel && (!enabled || !defined(str))) {
  20416. point.dataLabel = dataLabel = dataLabel.destroy();
  20417. if (connector) {
  20418. point.connector = connector.destroy();
  20419. }
  20420. // Individual labels are disabled if the are explicitly disabled
  20421. // in the point options, or if they fall outside the plot area.
  20422. } else if (enabled && defined(str)) {
  20423. // create new label
  20424. if (!dataLabel) {
  20425. dataLabel = point.dataLabel = renderer[rotation ? 'text' : 'label']( // labels don't support rotation
  20426. str,
  20427. 0, -9999,
  20428. options.shape,
  20429. null,
  20430. null,
  20431. options.useHTML,
  20432. null,
  20433. 'data-label'
  20434. );
  20435. dataLabel.addClass(
  20436. 'highcharts-data-label-color-' + point.colorIndex +
  20437. ' ' + (options.className || '') +
  20438. (options.useHTML ? 'highcharts-tracker' : '') // #3398
  20439. );
  20440. } else {
  20441. attr.text = str;
  20442. }
  20443. dataLabel.attr(attr);
  20444. // Styles must be applied before add in order to read text bounding box
  20445. dataLabel.css(style).shadow(options.shadow);
  20446. if (!dataLabel.added) {
  20447. dataLabel.add(dataLabelsGroup);
  20448. }
  20449. // Now the data label is created and placed at 0,0, so we need to align it
  20450. series.alignDataLabel(point, dataLabel, options, null, isNew);
  20451. }
  20452. });
  20453. }
  20454. };
  20455. /**
  20456. * Align each individual data label
  20457. */
  20458. Series.prototype.alignDataLabel = function(point, dataLabel, options, alignTo, isNew) {
  20459. var chart = this.chart,
  20460. inverted = chart.inverted,
  20461. plotX = pick(point.plotX, -9999),
  20462. plotY = pick(point.plotY, -9999),
  20463. bBox = dataLabel.getBBox(),
  20464. fontSize,
  20465. baseline,
  20466. rotation = options.rotation,
  20467. normRotation,
  20468. negRotation,
  20469. align = options.align,
  20470. rotCorr, // rotation correction
  20471. // Math.round for rounding errors (#2683), alignTo to allow column labels (#2700)
  20472. visible =
  20473. this.visible &&
  20474. (
  20475. point.series.forceDL ||
  20476. chart.isInsidePlot(plotX, Math.round(plotY), inverted) ||
  20477. (
  20478. alignTo && chart.isInsidePlot(
  20479. plotX,
  20480. inverted ? alignTo.x + 1 : alignTo.y + alignTo.height - 1,
  20481. inverted
  20482. )
  20483. )
  20484. ),
  20485. alignAttr, // the final position;
  20486. justify = pick(options.overflow, 'justify') === 'justify';
  20487. if (visible) {
  20488. fontSize = options.style.fontSize;
  20489. baseline = chart.renderer.fontMetrics(fontSize, dataLabel).b;
  20490. // The alignment box is a singular point
  20491. alignTo = extend({
  20492. x: inverted ? chart.plotWidth - plotY : plotX,
  20493. y: Math.round(inverted ? chart.plotHeight - plotX : plotY),
  20494. width: 0,
  20495. height: 0
  20496. }, alignTo);
  20497. // Add the text size for alignment calculation
  20498. extend(options, {
  20499. width: bBox.width,
  20500. height: bBox.height
  20501. });
  20502. // Allow a hook for changing alignment in the last moment, then do the alignment
  20503. if (rotation) {
  20504. justify = false; // Not supported for rotated text
  20505. rotCorr = chart.renderer.rotCorr(baseline, rotation); // #3723
  20506. alignAttr = {
  20507. x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x,
  20508. y: alignTo.y + options.y + {
  20509. top: 0,
  20510. middle: 0.5,
  20511. bottom: 1
  20512. }[options.verticalAlign] * alignTo.height
  20513. };
  20514. dataLabel[isNew ? 'attr' : 'animate'](alignAttr)
  20515. .attr({ // #3003
  20516. align: align
  20517. });
  20518. // Compensate for the rotated label sticking out on the sides
  20519. normRotation = (rotation + 720) % 360;
  20520. negRotation = normRotation > 180 && normRotation < 360;
  20521. if (align === 'left') {
  20522. alignAttr.y -= negRotation ? bBox.height : 0;
  20523. } else if (align === 'center') {
  20524. alignAttr.x -= bBox.width / 2;
  20525. alignAttr.y -= bBox.height / 2;
  20526. } else if (align === 'right') {
  20527. alignAttr.x -= bBox.width;
  20528. alignAttr.y -= negRotation ? 0 : bBox.height;
  20529. }
  20530. } else {
  20531. dataLabel.align(options, null, alignTo);
  20532. alignAttr = dataLabel.alignAttr;
  20533. }
  20534. // Handle justify or crop
  20535. if (justify) {
  20536. point.isLabelJustified = this.justifyDataLabel(
  20537. dataLabel,
  20538. options,
  20539. alignAttr,
  20540. bBox,
  20541. alignTo,
  20542. isNew
  20543. );
  20544. // Now check that the data label is within the plot area
  20545. } else if (pick(options.crop, true)) {
  20546. visible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height);
  20547. }
  20548. // When we're using a shape, make it possible with a connector or an arrow pointing to thie point
  20549. if (options.shape && !rotation) {
  20550. dataLabel[isNew ? 'attr' : 'animate']({
  20551. anchorX: inverted ? chart.plotWidth - point.plotY : point.plotX,
  20552. anchorY: inverted ? chart.plotHeight - point.plotX : point.plotY
  20553. });
  20554. }
  20555. }
  20556. // Show or hide based on the final aligned position
  20557. if (!visible) {
  20558. dataLabel.attr({
  20559. y: -9999
  20560. });
  20561. dataLabel.placed = false; // don't animate back in
  20562. }
  20563. };
  20564. /**
  20565. * If data labels fall partly outside the plot area, align them back in, in a way that
  20566. * doesn't hide the point.
  20567. */
  20568. Series.prototype.justifyDataLabel = function(dataLabel, options, alignAttr, bBox, alignTo, isNew) {
  20569. var chart = this.chart,
  20570. align = options.align,
  20571. verticalAlign = options.verticalAlign,
  20572. off,
  20573. justified,
  20574. padding = dataLabel.box ? 0 : (dataLabel.padding || 0);
  20575. // Off left
  20576. off = alignAttr.x + padding;
  20577. if (off < 0) {
  20578. if (align === 'right') {
  20579. options.align = 'left';
  20580. } else {
  20581. options.x = -off;
  20582. }
  20583. justified = true;
  20584. }
  20585. // Off right
  20586. off = alignAttr.x + bBox.width - padding;
  20587. if (off > chart.plotWidth) {
  20588. if (align === 'left') {
  20589. options.align = 'right';
  20590. } else {
  20591. options.x = chart.plotWidth - off;
  20592. }
  20593. justified = true;
  20594. }
  20595. // Off top
  20596. off = alignAttr.y + padding;
  20597. if (off < 0) {
  20598. if (verticalAlign === 'bottom') {
  20599. options.verticalAlign = 'top';
  20600. } else {
  20601. options.y = -off;
  20602. }
  20603. justified = true;
  20604. }
  20605. // Off bottom
  20606. off = alignAttr.y + bBox.height - padding;
  20607. if (off > chart.plotHeight) {
  20608. if (verticalAlign === 'top') {
  20609. options.verticalAlign = 'bottom';
  20610. } else {
  20611. options.y = chart.plotHeight - off;
  20612. }
  20613. justified = true;
  20614. }
  20615. if (justified) {
  20616. dataLabel.placed = !isNew;
  20617. dataLabel.align(options, null, alignTo);
  20618. }
  20619. return justified;
  20620. };
  20621. /**
  20622. * Override the base drawDataLabels method by pie specific functionality
  20623. */
  20624. if (seriesTypes.pie) {
  20625. seriesTypes.pie.prototype.drawDataLabels = function() {
  20626. var series = this,
  20627. data = series.data,
  20628. point,
  20629. chart = series.chart,
  20630. options = series.options.dataLabels,
  20631. connectorPadding = pick(options.connectorPadding, 10),
  20632. connectorWidth = pick(options.connectorWidth, 1),
  20633. plotWidth = chart.plotWidth,
  20634. plotHeight = chart.plotHeight,
  20635. connector,
  20636. seriesCenter = series.center,
  20637. radius = seriesCenter[2] / 2,
  20638. centerY = seriesCenter[1],
  20639. dataLabel,
  20640. dataLabelWidth,
  20641. labelPos,
  20642. labelHeight,
  20643. halves = [ // divide the points into right and left halves for anti collision
  20644. [], // right
  20645. [] // left
  20646. ],
  20647. x,
  20648. y,
  20649. visibility,
  20650. j,
  20651. overflow = [0, 0, 0, 0]; // top, right, bottom, left
  20652. // get out if not enabled
  20653. if (!series.visible || (!options.enabled && !series._hasPointLabels)) {
  20654. return;
  20655. }
  20656. // Reset all labels that have been shortened
  20657. each(data, function(point) {
  20658. if (point.dataLabel && point.visible && point.dataLabel.shortened) {
  20659. point.dataLabel
  20660. .attr({
  20661. width: 'auto'
  20662. }).css({
  20663. width: 'auto',
  20664. textOverflow: 'clip'
  20665. });
  20666. point.dataLabel.shortened = false;
  20667. }
  20668. });
  20669. // run parent method
  20670. Series.prototype.drawDataLabels.apply(series);
  20671. each(data, function(point) {
  20672. if (point.dataLabel && point.visible) { // #407, #2510
  20673. // Arrange points for detection collision
  20674. halves[point.half].push(point);
  20675. // Reset positions (#4905)
  20676. point.dataLabel._pos = null;
  20677. }
  20678. });
  20679. /* Loop over the points in each half, starting from the top and bottom
  20680. * of the pie to detect overlapping labels.
  20681. */
  20682. each(halves, function(points, i) {
  20683. var top,
  20684. bottom,
  20685. length = points.length,
  20686. positions = [],
  20687. naturalY,
  20688. sideOverflow,
  20689. positionsIndex, // Point index in positions array.
  20690. size;
  20691. if (!length) {
  20692. return;
  20693. }
  20694. // Sort by angle
  20695. series.sortByAngle(points, i - 0.5);
  20696. // Only do anti-collision when we have dataLabels outside the pie
  20697. // and have connectors. (#856)
  20698. if (series.maxLabelDistance > 0) {
  20699. top = Math.max(
  20700. 0,
  20701. centerY - radius - series.maxLabelDistance
  20702. );
  20703. bottom = Math.min(
  20704. centerY + radius + series.maxLabelDistance,
  20705. chart.plotHeight
  20706. );
  20707. each(points, function(point) {
  20708. // check if specific points' label is outside the pie
  20709. if (point.labelDistance > 0 && point.dataLabel) {
  20710. // point.top depends on point.labelDistance value
  20711. // Used for calculation of y value in getX method
  20712. point.top = Math.max(
  20713. 0,
  20714. centerY - radius - point.labelDistance
  20715. );
  20716. point.bottom = Math.min(
  20717. centerY + radius + point.labelDistance,
  20718. chart.plotHeight
  20719. );
  20720. size = point.dataLabel.getBBox().height || 21;
  20721. // point.positionsIndex is needed for getting index of
  20722. // parameter related to specific point inside positions
  20723. // array - not every point is in positions array.
  20724. point.positionsIndex = positions.push({
  20725. target: point.labelPos[1] - point.top + size / 2,
  20726. size: size,
  20727. rank: point.y
  20728. }) - 1;
  20729. }
  20730. });
  20731. H.distribute(positions, bottom + size - top);
  20732. }
  20733. // Now the used slots are sorted, fill them up sequentially
  20734. for (j = 0; j < length; j++) {
  20735. point = points[j];
  20736. positionsIndex = point.positionsIndex;
  20737. labelPos = point.labelPos;
  20738. dataLabel = point.dataLabel;
  20739. visibility = point.visible === false ? 'hidden' : 'inherit';
  20740. naturalY = labelPos[1];
  20741. if (positions && defined(positions[positionsIndex])) {
  20742. if (positions[positionsIndex].pos === undefined) {
  20743. visibility = 'hidden';
  20744. } else {
  20745. labelHeight = positions[positionsIndex].size;
  20746. y = point.top + positions[positionsIndex].pos;
  20747. }
  20748. } else {
  20749. y = naturalY;
  20750. }
  20751. // It is needed to delete point.positionIndex for
  20752. // dynamically added points etc.
  20753. delete point.positionIndex;
  20754. // get the x - use the natural x position for labels near the
  20755. // top and bottom, to prevent the top and botton slice connectors
  20756. // from touching each other on either side
  20757. if (options.justify) {
  20758. x = seriesCenter[0] + (i ? -1 : 1) * (radius + point.labelDistance);
  20759. } else {
  20760. x = series.getX(y < point.top + 2 || y > point.bottom - 2 ? naturalY : y, i, point);
  20761. }
  20762. // Record the placement and visibility
  20763. dataLabel._attr = {
  20764. visibility: visibility,
  20765. align: labelPos[6]
  20766. };
  20767. dataLabel._pos = {
  20768. x: x + options.x +
  20769. ({
  20770. left: connectorPadding,
  20771. right: -connectorPadding
  20772. }[labelPos[6]] || 0),
  20773. y: y + options.y - 10 // 10 is for the baseline (label vs text)
  20774. };
  20775. labelPos.x = x;
  20776. labelPos.y = y;
  20777. // Detect overflowing data labels
  20778. dataLabelWidth = dataLabel.getBBox().width;
  20779. sideOverflow = null;
  20780. // Overflow left
  20781. if (x - dataLabelWidth < connectorPadding) {
  20782. sideOverflow = Math.round(
  20783. dataLabelWidth - x + connectorPadding
  20784. );
  20785. overflow[3] = Math.max(sideOverflow, overflow[3]);
  20786. // Overflow right
  20787. } else if (x + dataLabelWidth > plotWidth - connectorPadding) {
  20788. sideOverflow = Math.round(
  20789. x + dataLabelWidth - plotWidth + connectorPadding
  20790. );
  20791. overflow[1] = Math.max(sideOverflow, overflow[1]);
  20792. }
  20793. // Overflow top
  20794. if (y - labelHeight / 2 < 0) {
  20795. overflow[0] = Math.max(
  20796. Math.round(-y + labelHeight / 2),
  20797. overflow[0]
  20798. );
  20799. // Overflow left
  20800. } else if (y + labelHeight / 2 > plotHeight) {
  20801. overflow[2] = Math.max(
  20802. Math.round(y + labelHeight / 2 - plotHeight),
  20803. overflow[2]
  20804. );
  20805. }
  20806. dataLabel.sideOverflow = sideOverflow;
  20807. } // for each point
  20808. }); // for each half
  20809. // Do not apply the final placement and draw the connectors until we have verified
  20810. // that labels are not spilling over.
  20811. if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) {
  20812. // Place the labels in the final position
  20813. this.placeDataLabels();
  20814. // Draw the connectors
  20815. if (connectorWidth) {
  20816. each(this.points, function(point) {
  20817. var isNew;
  20818. connector = point.connector;
  20819. dataLabel = point.dataLabel;
  20820. if (
  20821. dataLabel &&
  20822. dataLabel._pos &&
  20823. point.visible &&
  20824. point.labelDistance > 0
  20825. ) {
  20826. visibility = dataLabel._attr.visibility;
  20827. isNew = !connector;
  20828. if (isNew) {
  20829. point.connector = connector = chart.renderer.path()
  20830. .addClass('highcharts-data-label-connector highcharts-color-' + point.colorIndex)
  20831. .add(series.dataLabelsGroup);
  20832. connector.attr({
  20833. 'stroke-width': connectorWidth,
  20834. 'stroke': options.connectorColor || point.color || '#666666'
  20835. });
  20836. }
  20837. connector[isNew ? 'attr' : 'animate']({
  20838. d: series.connectorPath(point.labelPos)
  20839. });
  20840. connector.attr('visibility', visibility);
  20841. } else if (connector) {
  20842. point.connector = connector.destroy();
  20843. }
  20844. });
  20845. }
  20846. }
  20847. };
  20848. /**
  20849. * Extendable method for getting the path of the connector between the data label
  20850. * and the pie slice.
  20851. */
  20852. seriesTypes.pie.prototype.connectorPath = function(labelPos) {
  20853. var x = labelPos.x,
  20854. y = labelPos.y;
  20855. return pick(this.options.dataLabels.softConnector, true) ? [
  20856. 'M',
  20857. x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
  20858. 'C',
  20859. x, y, // first break, next to the label
  20860. 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
  20861. labelPos[2], labelPos[3], // second break
  20862. 'L',
  20863. labelPos[4], labelPos[5] // base
  20864. ] : [
  20865. 'M',
  20866. x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
  20867. 'L',
  20868. labelPos[2], labelPos[3], // second break
  20869. 'L',
  20870. labelPos[4], labelPos[5] // base
  20871. ];
  20872. };
  20873. /**
  20874. * Perform the final placement of the data labels after we have verified that they
  20875. * fall within the plot area.
  20876. */
  20877. seriesTypes.pie.prototype.placeDataLabels = function() {
  20878. each(this.points, function(point) {
  20879. var dataLabel = point.dataLabel,
  20880. _pos;
  20881. if (dataLabel && point.visible) {
  20882. _pos = dataLabel._pos;
  20883. if (_pos) {
  20884. // Shorten data labels with ellipsis if they still overflow
  20885. // after the pie has reached minSize (#223).
  20886. if (dataLabel.sideOverflow) {
  20887. dataLabel._attr.width =
  20888. dataLabel.getBBox().width - dataLabel.sideOverflow;
  20889. dataLabel.css({
  20890. width: dataLabel._attr.width + 'px',
  20891. textOverflow: 'ellipsis'
  20892. });
  20893. dataLabel.shortened = true;
  20894. }
  20895. dataLabel.attr(dataLabel._attr);
  20896. dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);
  20897. dataLabel.moved = true;
  20898. } else if (dataLabel) {
  20899. dataLabel.attr({
  20900. y: -9999
  20901. });
  20902. }
  20903. }
  20904. }, this);
  20905. };
  20906. seriesTypes.pie.prototype.alignDataLabel = noop;
  20907. /**
  20908. * Verify whether the data labels are allowed to draw, or we should run more translation and data
  20909. * label positioning to keep them inside the plot area. Returns true when data labels are ready
  20910. * to draw.
  20911. */
  20912. seriesTypes.pie.prototype.verifyDataLabelOverflow = function(overflow) {
  20913. var center = this.center,
  20914. options = this.options,
  20915. centerOption = options.center,
  20916. minSize = options.minSize || 80,
  20917. newSize = minSize,
  20918. // If a size is set, return true and don't try to shrink the pie
  20919. // to fit the labels.
  20920. ret = options.size !== null;
  20921. if (!ret) {
  20922. // Handle horizontal size and center
  20923. if (centerOption[0] !== null) { // Fixed center
  20924. newSize = Math.max(center[2] -
  20925. Math.max(overflow[1], overflow[3]), minSize);
  20926. } else { // Auto center
  20927. newSize = Math.max(
  20928. // horizontal overflow
  20929. center[2] - overflow[1] - overflow[3],
  20930. minSize
  20931. );
  20932. // horizontal center
  20933. center[0] += (overflow[3] - overflow[1]) / 2;
  20934. }
  20935. // Handle vertical size and center
  20936. if (centerOption[1] !== null) { // Fixed center
  20937. newSize = Math.max(Math.min(newSize, center[2] -
  20938. Math.max(overflow[0], overflow[2])), minSize);
  20939. } else { // Auto center
  20940. newSize = Math.max(
  20941. Math.min(
  20942. newSize,
  20943. // vertical overflow
  20944. center[2] - overflow[0] - overflow[2]
  20945. ),
  20946. minSize
  20947. );
  20948. // vertical center
  20949. center[1] += (overflow[0] - overflow[2]) / 2;
  20950. }
  20951. // If the size must be decreased, we need to run translate and
  20952. // drawDataLabels again
  20953. if (newSize < center[2]) {
  20954. center[2] = newSize;
  20955. center[3] = Math.min( // #3632
  20956. relativeLength(options.innerSize || 0, newSize),
  20957. newSize
  20958. );
  20959. this.translate(center);
  20960. if (this.drawDataLabels) {
  20961. this.drawDataLabels();
  20962. }
  20963. // Else, return true to indicate that the pie and its labels is
  20964. // within the plot area
  20965. } else {
  20966. ret = true;
  20967. }
  20968. }
  20969. return ret;
  20970. };
  20971. }
  20972. if (seriesTypes.column) {
  20973. /**
  20974. * Override the basic data label alignment by adjusting for the position of the column
  20975. */
  20976. seriesTypes.column.prototype.alignDataLabel = function(point, dataLabel, options, alignTo, isNew) {
  20977. var inverted = this.chart.inverted,
  20978. series = point.series,
  20979. dlBox = point.dlBox || point.shapeArgs, // data label box for alignment
  20980. below = pick(point.below, point.plotY > pick(this.translatedThreshold, series.yAxis.len)), // point.below is used in range series
  20981. inside = pick(options.inside, !!this.options.stacking), // draw it inside the box?
  20982. overshoot;
  20983. // Align to the column itself, or the top of it
  20984. if (dlBox) { // Area range uses this method but not alignTo
  20985. alignTo = merge(dlBox);
  20986. if (alignTo.y < 0) {
  20987. alignTo.height += alignTo.y;
  20988. alignTo.y = 0;
  20989. }
  20990. overshoot = alignTo.y + alignTo.height - series.yAxis.len;
  20991. if (overshoot > 0) {
  20992. alignTo.height -= overshoot;
  20993. }
  20994. if (inverted) {
  20995. alignTo = {
  20996. x: series.yAxis.len - alignTo.y - alignTo.height,
  20997. y: series.xAxis.len - alignTo.x - alignTo.width,
  20998. width: alignTo.height,
  20999. height: alignTo.width
  21000. };
  21001. }
  21002. // Compute the alignment box
  21003. if (!inside) {
  21004. if (inverted) {
  21005. alignTo.x += below ? 0 : alignTo.width;
  21006. alignTo.width = 0;
  21007. } else {
  21008. alignTo.y += below ? alignTo.height : 0;
  21009. alignTo.height = 0;
  21010. }
  21011. }
  21012. }
  21013. // When alignment is undefined (typically columns and bars), display the individual
  21014. // point below or above the point depending on the threshold
  21015. options.align = pick(
  21016. options.align, !inverted || inside ? 'center' : below ? 'right' : 'left'
  21017. );
  21018. options.verticalAlign = pick(
  21019. options.verticalAlign,
  21020. inverted || inside ? 'middle' : below ? 'top' : 'bottom'
  21021. );
  21022. // Call the parent method
  21023. Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
  21024. // If label was justified and we have contrast, set it:
  21025. if (point.isLabelJustified && point.contrastColor) {
  21026. point.dataLabel.css({
  21027. color: point.contrastColor
  21028. });
  21029. }
  21030. };
  21031. }
  21032. }(Highcharts));
  21033. (function(H) {
  21034. /**
  21035. * (c) 2009-2017 Torstein Honsi
  21036. *
  21037. * License: www.highcharts.com/license
  21038. */
  21039. /**
  21040. * Highcharts module to hide overlapping data labels. This module is included in
  21041. * Highcharts.
  21042. */
  21043. var Chart = H.Chart,
  21044. each = H.each,
  21045. pick = H.pick,
  21046. addEvent = H.addEvent;
  21047. // Collect potensial overlapping data labels. Stack labels probably don't need
  21048. // to be considered because they are usually accompanied by data labels that lie
  21049. // inside the columns.
  21050. Chart.prototype.callbacks.push(function(chart) {
  21051. function collectAndHide() {
  21052. var labels = [];
  21053. each(chart.series || [], function(series) {
  21054. var dlOptions = series.options.dataLabels,
  21055. // Range series have two collections
  21056. collections = series.dataLabelCollections || ['dataLabel'];
  21057. if (
  21058. (dlOptions.enabled || series._hasPointLabels) &&
  21059. !dlOptions.allowOverlap &&
  21060. series.visible
  21061. ) { // #3866
  21062. each(collections, function(coll) {
  21063. each(series.points, function(point) {
  21064. if (point[coll]) {
  21065. point[coll].labelrank = pick(
  21066. point.labelrank,
  21067. point.shapeArgs && point.shapeArgs.height
  21068. ); // #4118
  21069. labels.push(point[coll]);
  21070. }
  21071. });
  21072. });
  21073. }
  21074. });
  21075. chart.hideOverlappingLabels(labels);
  21076. }
  21077. // Do it now ...
  21078. collectAndHide();
  21079. // ... and after each chart redraw
  21080. addEvent(chart, 'redraw', collectAndHide);
  21081. });
  21082. /**
  21083. * Hide overlapping labels. Labels are moved and faded in and out on zoom to
  21084. * provide a smooth visual imression.
  21085. */
  21086. Chart.prototype.hideOverlappingLabels = function(labels) {
  21087. var len = labels.length,
  21088. label,
  21089. i,
  21090. j,
  21091. label1,
  21092. label2,
  21093. isIntersecting,
  21094. pos1,
  21095. pos2,
  21096. parent1,
  21097. parent2,
  21098. padding,
  21099. intersectRect = function(x1, y1, w1, h1, x2, y2, w2, h2) {
  21100. return !(
  21101. x2 > x1 + w1 ||
  21102. x2 + w2 < x1 ||
  21103. y2 > y1 + h1 ||
  21104. y2 + h2 < y1
  21105. );
  21106. };
  21107. // Mark with initial opacity
  21108. for (i = 0; i < len; i++) {
  21109. label = labels[i];
  21110. if (label) {
  21111. label.oldOpacity = label.opacity;
  21112. label.newOpacity = 1;
  21113. }
  21114. }
  21115. // Prevent a situation in a gradually rising slope, that each label will
  21116. // hide the previous one because the previous one always has lower rank.
  21117. labels.sort(function(a, b) {
  21118. return (b.labelrank || 0) - (a.labelrank || 0);
  21119. });
  21120. // Detect overlapping labels
  21121. for (i = 0; i < len; i++) {
  21122. label1 = labels[i];
  21123. for (j = i + 1; j < len; ++j) {
  21124. label2 = labels[j];
  21125. if (
  21126. label1 && label2 &&
  21127. label1 !== label2 && // #6465, polar chart with connectEnds
  21128. label1.placed && label2.placed &&
  21129. label1.newOpacity !== 0 && label2.newOpacity !== 0
  21130. ) {
  21131. pos1 = label1.alignAttr;
  21132. pos2 = label2.alignAttr;
  21133. // Different panes have different positions
  21134. parent1 = label1.parentGroup;
  21135. parent2 = label2.parentGroup;
  21136. // Substract the padding if no background or border (#4333)
  21137. padding = 2 * (label1.box ? 0 : label1.padding);
  21138. isIntersecting = intersectRect(
  21139. pos1.x + parent1.translateX,
  21140. pos1.y + parent1.translateY,
  21141. label1.width - padding,
  21142. label1.height - padding,
  21143. pos2.x + parent2.translateX,
  21144. pos2.y + parent2.translateY,
  21145. label2.width - padding,
  21146. label2.height - padding
  21147. );
  21148. if (isIntersecting) {
  21149. (label1.labelrank < label2.labelrank ? label1 : label2)
  21150. .newOpacity = 0;
  21151. }
  21152. }
  21153. }
  21154. }
  21155. // Hide or show
  21156. each(labels, function(label) {
  21157. var complete,
  21158. newOpacity;
  21159. if (label) {
  21160. newOpacity = label.newOpacity;
  21161. if (label.oldOpacity !== newOpacity && label.placed) {
  21162. // Make sure the label is completely hidden to avoid catching
  21163. // clicks (#4362)
  21164. if (newOpacity) {
  21165. label.show(true);
  21166. } else {
  21167. complete = function() {
  21168. label.hide();
  21169. };
  21170. }
  21171. // Animate or set the opacity
  21172. label.alignAttr.opacity = newOpacity;
  21173. label[label.isOld ? 'animate' : 'attr'](
  21174. label.alignAttr,
  21175. null,
  21176. complete
  21177. );
  21178. }
  21179. label.isOld = true;
  21180. }
  21181. });
  21182. };
  21183. }(Highcharts));
  21184. (function(H) {
  21185. /**
  21186. * (c) 2010-2017 Torstein Honsi
  21187. *
  21188. * License: www.highcharts.com/license
  21189. */
  21190. var addEvent = H.addEvent,
  21191. Chart = H.Chart,
  21192. createElement = H.createElement,
  21193. css = H.css,
  21194. defaultOptions = H.defaultOptions,
  21195. defaultPlotOptions = H.defaultPlotOptions,
  21196. each = H.each,
  21197. extend = H.extend,
  21198. fireEvent = H.fireEvent,
  21199. hasTouch = H.hasTouch,
  21200. inArray = H.inArray,
  21201. isObject = H.isObject,
  21202. Legend = H.Legend,
  21203. merge = H.merge,
  21204. pick = H.pick,
  21205. Point = H.Point,
  21206. Series = H.Series,
  21207. seriesTypes = H.seriesTypes,
  21208. svg = H.svg,
  21209. TrackerMixin;
  21210. /**
  21211. * TrackerMixin for points and graphs.
  21212. *
  21213. * @mixin
  21214. */
  21215. TrackerMixin = H.TrackerMixin = {
  21216. /**
  21217. * Draw the tracker for a point.
  21218. */
  21219. drawTrackerPoint: function() {
  21220. var series = this,
  21221. chart = series.chart,
  21222. pointer = chart.pointer,
  21223. onMouseOver = function(e) {
  21224. var point = pointer.getPointFromEvent(e);
  21225. // undefined on graph in scatterchart
  21226. if (point !== undefined) {
  21227. pointer.isDirectTouch = true;
  21228. point.onMouseOver(e);
  21229. }
  21230. };
  21231. // Add reference to the point
  21232. each(series.points, function(point) {
  21233. if (point.graphic) {
  21234. point.graphic.element.point = point;
  21235. }
  21236. if (point.dataLabel) {
  21237. if (point.dataLabel.div) {
  21238. point.dataLabel.div.point = point;
  21239. } else {
  21240. point.dataLabel.element.point = point;
  21241. }
  21242. }
  21243. });
  21244. // Add the event listeners, we need to do this only once
  21245. if (!series._hasTracking) {
  21246. each(series.trackerGroups, function(key) {
  21247. if (series[key]) { // we don't always have dataLabelsGroup
  21248. series[key]
  21249. .addClass('highcharts-tracker')
  21250. .on('mouseover', onMouseOver)
  21251. .on('mouseout', function(e) {
  21252. pointer.onTrackerMouseOut(e);
  21253. });
  21254. if (hasTouch) {
  21255. series[key].on('touchstart', onMouseOver);
  21256. }
  21257. if (series.options.cursor) {
  21258. series[key]
  21259. .css(css)
  21260. .css({
  21261. cursor: series.options.cursor
  21262. });
  21263. }
  21264. }
  21265. });
  21266. series._hasTracking = true;
  21267. }
  21268. },
  21269. /**
  21270. * Draw the tracker object that sits above all data labels and markers to
  21271. * track mouse events on the graph or points. For the line type charts
  21272. * the tracker uses the same graphPath, but with a greater stroke width
  21273. * for better control.
  21274. */
  21275. drawTrackerGraph: function() {
  21276. var series = this,
  21277. options = series.options,
  21278. trackByArea = options.trackByArea,
  21279. trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
  21280. trackerPathLength = trackerPath.length,
  21281. chart = series.chart,
  21282. pointer = chart.pointer,
  21283. renderer = chart.renderer,
  21284. snap = chart.options.tooltip.snap,
  21285. tracker = series.tracker,
  21286. i,
  21287. onMouseOver = function() {
  21288. if (chart.hoverSeries !== series) {
  21289. series.onMouseOver();
  21290. }
  21291. },
  21292. /*
  21293. * Empirical lowest possible opacities for TRACKER_FILL for an element to stay invisible but clickable
  21294. * IE6: 0.002
  21295. * IE7: 0.002
  21296. * IE8: 0.002
  21297. * IE9: 0.00000000001 (unlimited)
  21298. * IE10: 0.0001 (exporting only)
  21299. * FF: 0.00000000001 (unlimited)
  21300. * Chrome: 0.000001
  21301. * Safari: 0.000001
  21302. * Opera: 0.00000000001 (unlimited)
  21303. */
  21304. TRACKER_FILL = 'rgba(192,192,192,' + (svg ? 0.0001 : 0.002) + ')';
  21305. // Extend end points. A better way would be to use round linecaps,
  21306. // but those are not clickable in VML.
  21307. if (trackerPathLength && !trackByArea) {
  21308. i = trackerPathLength + 1;
  21309. while (i--) {
  21310. if (trackerPath[i] === 'M') { // extend left side
  21311. trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], 'L');
  21312. }
  21313. if ((i && trackerPath[i] === 'M') || i === trackerPathLength) { // extend right side
  21314. trackerPath.splice(i, 0, 'L', trackerPath[i - 2] + snap, trackerPath[i - 1]);
  21315. }
  21316. }
  21317. }
  21318. // handle single points
  21319. /*for (i = 0; i < singlePoints.length; i++) {
  21320. singlePoint = singlePoints[i];
  21321. trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
  21322. L, singlePoint.plotX + snap, singlePoint.plotY);
  21323. }*/
  21324. // draw the tracker
  21325. if (tracker) {
  21326. tracker.attr({
  21327. d: trackerPath
  21328. });
  21329. } else if (series.graph) { // create
  21330. series.tracker = renderer.path(trackerPath)
  21331. .attr({
  21332. 'stroke-linejoin': 'round', // #1225
  21333. visibility: series.visible ? 'visible' : 'hidden',
  21334. stroke: TRACKER_FILL,
  21335. fill: trackByArea ? TRACKER_FILL : 'none',
  21336. 'stroke-width': series.graph.strokeWidth() + (trackByArea ? 0 : 2 * snap),
  21337. zIndex: 2
  21338. })
  21339. .add(series.group);
  21340. // The tracker is added to the series group, which is clipped, but is covered
  21341. // by the marker group. So the marker group also needs to capture events.
  21342. each([series.tracker, series.markerGroup], function(tracker) {
  21343. tracker.addClass('highcharts-tracker')
  21344. .on('mouseover', onMouseOver)
  21345. .on('mouseout', function(e) {
  21346. pointer.onTrackerMouseOut(e);
  21347. });
  21348. if (options.cursor) {
  21349. tracker.css({
  21350. cursor: options.cursor
  21351. });
  21352. }
  21353. if (hasTouch) {
  21354. tracker.on('touchstart', onMouseOver);
  21355. }
  21356. });
  21357. }
  21358. }
  21359. };
  21360. /* End TrackerMixin */
  21361. /**
  21362. * Add tracking event listener to the series group, so the point graphics
  21363. * themselves act as trackers
  21364. */
  21365. if (seriesTypes.column) {
  21366. seriesTypes.column.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
  21367. }
  21368. if (seriesTypes.pie) {
  21369. seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
  21370. }
  21371. if (seriesTypes.scatter) {
  21372. seriesTypes.scatter.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
  21373. }
  21374. /*
  21375. * Extend Legend for item events
  21376. */
  21377. extend(Legend.prototype, {
  21378. setItemEvents: function(item, legendItem, useHTML) {
  21379. var legend = this,
  21380. boxWrapper = legend.chart.renderer.boxWrapper,
  21381. activeClass = 'highcharts-legend-' + (item.series ? 'point' : 'series') + '-active';
  21382. // Set the events on the item group, or in case of useHTML, the item itself (#1249)
  21383. (useHTML ? legendItem : item.legendGroup).on('mouseover', function() {
  21384. item.setState('hover');
  21385. // A CSS class to dim or hide other than the hovered series
  21386. boxWrapper.addClass(activeClass);
  21387. legendItem.css(legend.options.itemHoverStyle);
  21388. })
  21389. .on('mouseout', function() {
  21390. legendItem.css(merge(item.visible ? legend.itemStyle : legend.itemHiddenStyle));
  21391. // A CSS class to dim or hide other than the hovered series
  21392. boxWrapper.removeClass(activeClass);
  21393. item.setState();
  21394. })
  21395. .on('click', function(event) {
  21396. var strLegendItemClick = 'legendItemClick',
  21397. fnLegendItemClick = function() {
  21398. if (item.setVisible) {
  21399. item.setVisible();
  21400. }
  21401. };
  21402. // Pass over the click/touch event. #4.
  21403. event = {
  21404. browserEvent: event
  21405. };
  21406. // click the name or symbol
  21407. if (item.firePointEvent) { // point
  21408. item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
  21409. } else {
  21410. fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
  21411. }
  21412. });
  21413. },
  21414. createCheckboxForItem: function(item) {
  21415. var legend = this;
  21416. item.checkbox = createElement('input', {
  21417. type: 'checkbox',
  21418. checked: item.selected,
  21419. defaultChecked: item.selected // required by IE7
  21420. }, legend.options.itemCheckboxStyle, legend.chart.container);
  21421. addEvent(item.checkbox, 'click', function(event) {
  21422. var target = event.target;
  21423. fireEvent(
  21424. item.series || item,
  21425. 'checkboxClick', { // #3712
  21426. checked: target.checked,
  21427. item: item
  21428. },
  21429. function() {
  21430. item.select();
  21431. }
  21432. );
  21433. });
  21434. }
  21435. });
  21436. // Add pointer cursor to legend itemstyle in defaultOptions
  21437. defaultOptions.legend.itemStyle.cursor = 'pointer';
  21438. /*
  21439. * Extend the Chart object with interaction
  21440. */
  21441. extend(Chart.prototype, /** @lends Chart.prototype */ {
  21442. /**
  21443. * Display the zoom button
  21444. */
  21445. showResetZoom: function() {
  21446. var chart = this,
  21447. lang = defaultOptions.lang,
  21448. btnOptions = chart.options.chart.resetZoomButton,
  21449. theme = btnOptions.theme,
  21450. states = theme.states,
  21451. alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';
  21452. function zoomOut() {
  21453. chart.zoomOut();
  21454. }
  21455. this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover)
  21456. .attr({
  21457. align: btnOptions.position.align,
  21458. title: lang.resetZoomTitle
  21459. })
  21460. .addClass('highcharts-reset-zoom')
  21461. .add()
  21462. .align(btnOptions.position, false, alignTo);
  21463. },
  21464. /**
  21465. * Zoom out to 1:1
  21466. */
  21467. zoomOut: function() {
  21468. var chart = this;
  21469. fireEvent(chart, 'selection', {
  21470. resetSelection: true
  21471. }, function() {
  21472. chart.zoom();
  21473. });
  21474. },
  21475. /**
  21476. * Zoom into a given portion of the chart given by axis coordinates
  21477. * @param {Object} event
  21478. */
  21479. zoom: function(event) {
  21480. var chart = this,
  21481. hasZoomed,
  21482. pointer = chart.pointer,
  21483. displayButton = false,
  21484. resetZoomButton;
  21485. // If zoom is called with no arguments, reset the axes
  21486. if (!event || event.resetSelection) {
  21487. each(chart.axes, function(axis) {
  21488. hasZoomed = axis.zoom();
  21489. });
  21490. } else { // else, zoom in on all axes
  21491. each(event.xAxis.concat(event.yAxis), function(axisData) {
  21492. var axis = axisData.axis,
  21493. isXAxis = axis.isXAxis;
  21494. // don't zoom more than minRange
  21495. if (pointer[isXAxis ? 'zoomX' : 'zoomY']) {
  21496. hasZoomed = axis.zoom(axisData.min, axisData.max);
  21497. if (axis.displayBtn) {
  21498. displayButton = true;
  21499. }
  21500. }
  21501. });
  21502. }
  21503. // Show or hide the Reset zoom button
  21504. resetZoomButton = chart.resetZoomButton;
  21505. if (displayButton && !resetZoomButton) {
  21506. chart.showResetZoom();
  21507. } else if (!displayButton && isObject(resetZoomButton)) {
  21508. chart.resetZoomButton = resetZoomButton.destroy();
  21509. }
  21510. // Redraw
  21511. if (hasZoomed) {
  21512. chart.redraw(
  21513. pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation
  21514. );
  21515. }
  21516. },
  21517. /**
  21518. * Pan the chart by dragging the mouse across the pane. This function is called
  21519. * on mouse move, and the distance to pan is computed from chartX compared to
  21520. * the first chartX position in the dragging operation.
  21521. */
  21522. pan: function(e, panning) {
  21523. var chart = this,
  21524. hoverPoints = chart.hoverPoints,
  21525. doRedraw;
  21526. // remove active points for shared tooltip
  21527. if (hoverPoints) {
  21528. each(hoverPoints, function(point) {
  21529. point.setState();
  21530. });
  21531. }
  21532. each(panning === 'xy' ? [1, 0] : [1], function(isX) { // xy is used in maps
  21533. var axis = chart[isX ? 'xAxis' : 'yAxis'][0],
  21534. horiz = axis.horiz,
  21535. mousePos = e[horiz ? 'chartX' : 'chartY'],
  21536. mouseDown = horiz ? 'mouseDownX' : 'mouseDownY',
  21537. startPos = chart[mouseDown],
  21538. halfPointRange = (axis.pointRange || 0) / 2,
  21539. extremes = axis.getExtremes(),
  21540. panMin = axis.toValue(startPos - mousePos, true) +
  21541. halfPointRange,
  21542. panMax = axis.toValue(startPos + axis.len - mousePos, true) -
  21543. halfPointRange,
  21544. flipped = panMax < panMin,
  21545. newMin = flipped ? panMax : panMin,
  21546. newMax = flipped ? panMin : panMax,
  21547. paddedMin = Math.min(
  21548. extremes.dataMin,
  21549. axis.toValue(
  21550. axis.toPixels(extremes.min) - axis.minPixelPadding
  21551. )
  21552. ),
  21553. paddedMax = Math.max(
  21554. extremes.dataMax,
  21555. axis.toValue(
  21556. axis.toPixels(extremes.max) + axis.minPixelPadding
  21557. )
  21558. ),
  21559. spill;
  21560. // If the new range spills over, either to the min or max, adjust
  21561. // the new range.
  21562. spill = paddedMin - newMin;
  21563. if (spill > 0) {
  21564. newMax += spill;
  21565. newMin = paddedMin;
  21566. }
  21567. spill = newMax - paddedMax;
  21568. if (spill > 0) {
  21569. newMax = paddedMax;
  21570. newMin -= spill;
  21571. }
  21572. // Set new extremes if they are actually new
  21573. if (axis.series.length && newMin !== extremes.min && newMax !== extremes.max) {
  21574. axis.setExtremes(
  21575. newMin,
  21576. newMax,
  21577. false,
  21578. false, {
  21579. trigger: 'pan'
  21580. }
  21581. );
  21582. doRedraw = true;
  21583. }
  21584. chart[mouseDown] = mousePos; // set new reference for next run
  21585. });
  21586. if (doRedraw) {
  21587. chart.redraw(false);
  21588. }
  21589. css(chart.container, {
  21590. cursor: 'move'
  21591. });
  21592. }
  21593. });
  21594. /*
  21595. * Extend the Point object with interaction
  21596. */
  21597. extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
  21598. /**
  21599. * Toggle the selection status of a point.
  21600. * @param {Boolean} [selected]
  21601. * When `true`, the point is selected. When `false`, the point is
  21602. * unselected. When `null` or `undefined`, the selection state is
  21603. * toggled.
  21604. * @param {Boolean} [accumulate=false]
  21605. * When `true`, the selection is added to other selected points.
  21606. * When `false`, other selected points are deselected. Internally in
  21607. * Highcharts, when {@link http://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect|allowPointSelect}
  21608. * is `true`, selected points are accumulated on Control, Shift or
  21609. * Cmd clicking the point.
  21610. *
  21611. * @see Highcharts.Chart#getSelectedPoints
  21612. *
  21613. * @sample highcharts/members/point-select/
  21614. * Select a point from a button
  21615. * @sample highcharts/chart/events-selection-points/
  21616. * Select a range of points through a drag selection
  21617. * @sample maps/series/data-id/
  21618. * Select a point in Highmaps
  21619. */
  21620. select: function(selected, accumulate) {
  21621. var point = this,
  21622. series = point.series,
  21623. chart = series.chart;
  21624. selected = pick(selected, !point.selected);
  21625. // fire the event with the default handler
  21626. point.firePointEvent(selected ? 'select' : 'unselect', {
  21627. accumulate: accumulate
  21628. }, function() {
  21629. /**
  21630. * Whether the point is selected or not.
  21631. * @see Highcharts.Point#select
  21632. * @memberof Highcharts.Point
  21633. * @name selected
  21634. * @type {Boolean}
  21635. */
  21636. point.selected = point.options.selected = selected;
  21637. series.options.data[inArray(point, series.data)] = point.options;
  21638. point.setState(selected && 'select');
  21639. // unselect all other points unless Ctrl or Cmd + click
  21640. if (!accumulate) {
  21641. each(chart.getSelectedPoints(), function(loopPoint) {
  21642. if (loopPoint.selected && loopPoint !== point) {
  21643. loopPoint.selected = loopPoint.options.selected = false;
  21644. series.options.data[inArray(loopPoint, series.data)] = loopPoint.options;
  21645. loopPoint.setState('');
  21646. loopPoint.firePointEvent('unselect');
  21647. }
  21648. });
  21649. }
  21650. });
  21651. },
  21652. /**
  21653. * Runs on mouse over the point
  21654. *
  21655. * @param {Object} e The event arguments
  21656. */
  21657. onMouseOver: function(e) {
  21658. var point = this,
  21659. series = point.series,
  21660. chart = series.chart,
  21661. pointer = chart.pointer;
  21662. e = e ?
  21663. pointer.normalize(e) :
  21664. // In cases where onMouseOver is called directly without an event
  21665. pointer.getChartCoordinatesFromPoint(point, chart.inverted);
  21666. pointer.runPointActions(e, point);
  21667. },
  21668. /**
  21669. * Runs on mouse out from the point
  21670. */
  21671. onMouseOut: function() {
  21672. var point = this,
  21673. chart = point.series.chart;
  21674. point.firePointEvent('mouseOut');
  21675. each(chart.hoverPoints || [], function(p) {
  21676. p.setState();
  21677. });
  21678. chart.hoverPoints = chart.hoverPoint = null;
  21679. },
  21680. /**
  21681. * Import events from the series' and point's options. Only do it on
  21682. * demand, to save processing time on hovering.
  21683. */
  21684. importEvents: function() {
  21685. if (!this.hasImportedEvents) {
  21686. var point = this,
  21687. options = merge(point.series.options.point, point.options),
  21688. events = options.events;
  21689. point.events = events;
  21690. H.objectEach(events, function(event, eventType) {
  21691. addEvent(point, eventType, event);
  21692. });
  21693. this.hasImportedEvents = true;
  21694. }
  21695. },
  21696. /**
  21697. * Set the point's state
  21698. * @param {String} state
  21699. */
  21700. setState: function(state, move) {
  21701. var point = this,
  21702. plotX = Math.floor(point.plotX), // #4586
  21703. plotY = point.plotY,
  21704. series = point.series,
  21705. stateOptions = series.options.states[state] || {},
  21706. markerOptions = defaultPlotOptions[series.type].marker &&
  21707. series.options.marker,
  21708. normalDisabled = markerOptions && markerOptions.enabled === false,
  21709. markerStateOptions = (markerOptions && markerOptions.states &&
  21710. markerOptions.states[state]) || {},
  21711. stateDisabled = markerStateOptions.enabled === false,
  21712. stateMarkerGraphic = series.stateMarkerGraphic,
  21713. pointMarker = point.marker || {},
  21714. chart = series.chart,
  21715. halo = series.halo,
  21716. haloOptions,
  21717. markerAttribs,
  21718. hasMarkers = markerOptions && series.markerAttribs,
  21719. newSymbol;
  21720. state = state || ''; // empty string
  21721. if (
  21722. // already has this state
  21723. (state === point.state && !move) ||
  21724. // selected points don't respond to hover
  21725. (point.selected && state !== 'select') ||
  21726. // series' state options is disabled
  21727. (stateOptions.enabled === false) ||
  21728. // general point marker's state options is disabled
  21729. (state && (stateDisabled || (normalDisabled && markerStateOptions.enabled === false))) ||
  21730. // individual point marker's state options is disabled
  21731. (state && pointMarker.states && pointMarker.states[state] && pointMarker.states[state].enabled === false) // #1610
  21732. ) {
  21733. return;
  21734. }
  21735. if (hasMarkers) {
  21736. markerAttribs = series.markerAttribs(point, state);
  21737. }
  21738. // Apply hover styles to the existing point
  21739. if (point.graphic) {
  21740. if (point.state) {
  21741. point.graphic.removeClass('highcharts-point-' + point.state);
  21742. }
  21743. if (state) {
  21744. point.graphic.addClass('highcharts-point-' + state);
  21745. }
  21746. /*attribs = radius ? { // new symbol attributes (#507, #612)
  21747. x: plotX - radius,
  21748. y: plotY - radius,
  21749. width: 2 * radius,
  21750. height: 2 * radius
  21751. } : {};*/
  21752. //attribs = merge(series.pointAttribs(point, state), attribs);
  21753. point.graphic.attr(series.pointAttribs(point, state));
  21754. if (markerAttribs) {
  21755. point.graphic.animate(
  21756. markerAttribs,
  21757. pick(
  21758. chart.options.chart.animation, // Turn off globally
  21759. markerStateOptions.animation,
  21760. markerOptions.animation
  21761. )
  21762. );
  21763. }
  21764. // Zooming in from a range with no markers to a range with markers
  21765. if (stateMarkerGraphic) {
  21766. stateMarkerGraphic.hide();
  21767. }
  21768. } else {
  21769. // if a graphic is not applied to each point in the normal state, create a shared
  21770. // graphic for the hover state
  21771. if (state && markerStateOptions) {
  21772. newSymbol = pointMarker.symbol || series.symbol;
  21773. // If the point has another symbol than the previous one, throw away the
  21774. // state marker graphic and force a new one (#1459)
  21775. if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {
  21776. stateMarkerGraphic = stateMarkerGraphic.destroy();
  21777. }
  21778. // Add a new state marker graphic
  21779. if (!stateMarkerGraphic) {
  21780. if (newSymbol) {
  21781. series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
  21782. newSymbol,
  21783. markerAttribs.x,
  21784. markerAttribs.y,
  21785. markerAttribs.width,
  21786. markerAttribs.height
  21787. )
  21788. .add(series.markerGroup);
  21789. stateMarkerGraphic.currentSymbol = newSymbol;
  21790. }
  21791. // Move the existing graphic
  21792. } else {
  21793. stateMarkerGraphic[move ? 'animate' : 'attr']({ // #1054
  21794. x: markerAttribs.x,
  21795. y: markerAttribs.y
  21796. });
  21797. }
  21798. if (stateMarkerGraphic) {
  21799. stateMarkerGraphic.attr(series.pointAttribs(point, state));
  21800. }
  21801. }
  21802. if (stateMarkerGraphic) {
  21803. stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY, chart.inverted) ? 'show' : 'hide'](); // #2450
  21804. stateMarkerGraphic.element.point = point; // #4310
  21805. }
  21806. }
  21807. // Show me your halo
  21808. haloOptions = stateOptions.halo;
  21809. if (haloOptions && haloOptions.size) {
  21810. if (!halo) {
  21811. series.halo = halo = chart.renderer.path()
  21812. // #5818, #5903, #6705
  21813. .add((point.graphic || stateMarkerGraphic).parentGroup);
  21814. }
  21815. halo[move ? 'animate' : 'attr']({
  21816. d: point.haloPath(haloOptions.size)
  21817. });
  21818. halo.attr({
  21819. 'class': 'highcharts-halo highcharts-color-' +
  21820. pick(point.colorIndex, series.colorIndex)
  21821. });
  21822. halo.point = point; // #6055
  21823. halo.attr(extend({
  21824. 'fill': point.color || series.color,
  21825. 'fill-opacity': haloOptions.opacity,
  21826. 'zIndex': -1 // #4929, IE8 added halo above everything
  21827. }, haloOptions.attributes));
  21828. } else if (halo && halo.point && halo.point.haloPath) {
  21829. // Animate back to 0 on the current halo point (#6055)
  21830. halo.animate({
  21831. d: halo.point.haloPath(0)
  21832. });
  21833. }
  21834. point.state = state;
  21835. },
  21836. /**
  21837. * Get the circular path definition for the halo
  21838. * @param {Number} size The radius of the circular halo.
  21839. * @returns {Array} The path definition
  21840. */
  21841. haloPath: function(size) {
  21842. var series = this.series,
  21843. chart = series.chart;
  21844. return chart.renderer.symbols.circle(
  21845. Math.floor(this.plotX) - size,
  21846. this.plotY - size,
  21847. size * 2,
  21848. size * 2
  21849. );
  21850. }
  21851. });
  21852. /*
  21853. * Extend the Series object with interaction
  21854. */
  21855. extend(Series.prototype, /** @lends Highcharts.Series.prototype */ {
  21856. /**
  21857. * Series mouse over handler
  21858. */
  21859. onMouseOver: function() {
  21860. var series = this,
  21861. chart = series.chart,
  21862. hoverSeries = chart.hoverSeries;
  21863. // set normal state to previous series
  21864. if (hoverSeries && hoverSeries !== series) {
  21865. hoverSeries.onMouseOut();
  21866. }
  21867. // trigger the event, but to save processing time,
  21868. // only if defined
  21869. if (series.options.events.mouseOver) {
  21870. fireEvent(series, 'mouseOver');
  21871. }
  21872. // hover this
  21873. series.setState('hover');
  21874. chart.hoverSeries = series;
  21875. },
  21876. /**
  21877. * Series mouse out handler
  21878. */
  21879. onMouseOut: function() {
  21880. // trigger the event only if listeners exist
  21881. var series = this,
  21882. options = series.options,
  21883. chart = series.chart,
  21884. tooltip = chart.tooltip,
  21885. hoverPoint = chart.hoverPoint;
  21886. chart.hoverSeries = null; // #182, set to null before the mouseOut event fires
  21887. // trigger mouse out on the point, which must be in this series
  21888. if (hoverPoint) {
  21889. hoverPoint.onMouseOut();
  21890. }
  21891. // fire the mouse out event
  21892. if (series && options.events.mouseOut) {
  21893. fireEvent(series, 'mouseOut');
  21894. }
  21895. // hide the tooltip
  21896. if (tooltip && !series.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {
  21897. tooltip.hide();
  21898. }
  21899. // set normal state
  21900. series.setState();
  21901. },
  21902. /**
  21903. * Set the state of the graph
  21904. */
  21905. setState: function(state) {
  21906. var series = this,
  21907. options = series.options,
  21908. graph = series.graph,
  21909. stateOptions = options.states,
  21910. lineWidth = options.lineWidth,
  21911. attribs,
  21912. i = 0;
  21913. state = state || '';
  21914. if (series.state !== state) {
  21915. // Toggle class names
  21916. each([
  21917. series.group,
  21918. series.markerGroup,
  21919. series.dataLabelsGroup
  21920. ], function(group) {
  21921. if (group) {
  21922. // Old state
  21923. if (series.state) {
  21924. group.removeClass('highcharts-series-' + series.state);
  21925. }
  21926. // New state
  21927. if (state) {
  21928. group.addClass('highcharts-series-' + state);
  21929. }
  21930. }
  21931. });
  21932. series.state = state;
  21933. if (stateOptions[state] && stateOptions[state].enabled === false) {
  21934. return;
  21935. }
  21936. if (state) {
  21937. lineWidth = stateOptions[state].lineWidth || lineWidth + (stateOptions[state].lineWidthPlus || 0); // #4035
  21938. }
  21939. if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
  21940. attribs = {
  21941. 'stroke-width': lineWidth
  21942. };
  21943. // Animate the graph stroke-width. By default a quick animation
  21944. // to hover, slower to un-hover.
  21945. graph.animate(
  21946. attribs,
  21947. pick(
  21948. series.chart.options.chart.animation,
  21949. stateOptions[state] && stateOptions[state].animation
  21950. )
  21951. );
  21952. while (series['zone-graph-' + i]) {
  21953. series['zone-graph-' + i].attr(attribs);
  21954. i = i + 1;
  21955. }
  21956. }
  21957. }
  21958. },
  21959. /**
  21960. * Show or hide the series.
  21961. *
  21962. * @param {Boolean} [visible]
  21963. * True to show the series, false to hide. If undefined, the
  21964. * visibility is toggled.
  21965. * @param {Boolean} [redraw=true]
  21966. * Whether to redraw the chart after the series is altered. If doing
  21967. * more operations on the chart, it is a good idea to set redraw to
  21968. * false and call {@link Chart#redraw|chart.redraw()} after.
  21969. */
  21970. setVisible: function(vis, redraw) {
  21971. var series = this,
  21972. chart = series.chart,
  21973. legendItem = series.legendItem,
  21974. showOrHide,
  21975. ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
  21976. oldVisibility = series.visible;
  21977. // if called without an argument, toggle visibility
  21978. series.visible = vis = series.options.visible = series.userOptions.visible = vis === undefined ? !oldVisibility : vis; // #5618
  21979. showOrHide = vis ? 'show' : 'hide';
  21980. // show or hide elements
  21981. each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker', 'tt'], function(key) {
  21982. if (series[key]) {
  21983. series[key][showOrHide]();
  21984. }
  21985. });
  21986. // hide tooltip (#1361)
  21987. if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) {
  21988. series.onMouseOut();
  21989. }
  21990. if (legendItem) {
  21991. chart.legend.colorizeItem(series, vis);
  21992. }
  21993. // rescale or adapt to resized chart
  21994. series.isDirty = true;
  21995. // in a stack, all other series are affected
  21996. if (series.options.stacking) {
  21997. each(chart.series, function(otherSeries) {
  21998. if (otherSeries.options.stacking && otherSeries.visible) {
  21999. otherSeries.isDirty = true;
  22000. }
  22001. });
  22002. }
  22003. // show or hide linked series
  22004. each(series.linkedSeries, function(otherSeries) {
  22005. otherSeries.setVisible(vis, false);
  22006. });
  22007. if (ignoreHiddenSeries) {
  22008. chart.isDirtyBox = true;
  22009. }
  22010. if (redraw !== false) {
  22011. chart.redraw();
  22012. }
  22013. fireEvent(series, showOrHide);
  22014. },
  22015. /**
  22016. * Show the series if hidden.
  22017. *
  22018. * @sample highcharts/members/series-hide/
  22019. * Toggle visibility from a button
  22020. */
  22021. show: function() {
  22022. this.setVisible(true);
  22023. },
  22024. /**
  22025. * Hide the series if visible. If the {@link
  22026. * https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries|
  22027. * chart.ignoreHiddenSeries} option is true, the chart is redrawn without
  22028. * this series.
  22029. *
  22030. * @sample highcharts/members/series-hide/
  22031. * Toggle visibility from a button
  22032. */
  22033. hide: function() {
  22034. this.setVisible(false);
  22035. },
  22036. /**
  22037. * Select or unselect the series. This means its {@link
  22038. * Highcharts.Series.selected|selected} property is set, the checkbox in the
  22039. * legend is toggled and when selected, the series is returned by the
  22040. * {@link Highcharts.Chart#getSelectedSeries} function.
  22041. *
  22042. * @param {Boolean} [selected]
  22043. * True to select the series, false to unselect. If undefined, the
  22044. * selection state is toggled.
  22045. *
  22046. * @sample highcharts/members/series-select/
  22047. * Select a series from a button
  22048. */
  22049. select: function(selected) {
  22050. var series = this;
  22051. series.selected = selected = (selected === undefined) ?
  22052. !series.selected :
  22053. selected;
  22054. if (series.checkbox) {
  22055. series.checkbox.checked = selected;
  22056. }
  22057. fireEvent(series, selected ? 'select' : 'unselect');
  22058. },
  22059. drawTracker: TrackerMixin.drawTrackerGraph
  22060. });
  22061. }(Highcharts));
  22062. (function(H) {
  22063. /**
  22064. * (c) 2010-2017 Torstein Honsi
  22065. *
  22066. * License: www.highcharts.com/license
  22067. */
  22068. var Chart = H.Chart,
  22069. each = H.each,
  22070. inArray = H.inArray,
  22071. isArray = H.isArray,
  22072. isObject = H.isObject,
  22073. pick = H.pick,
  22074. splat = H.splat;
  22075. /**
  22076. * Update the chart based on the current chart/document size and options for
  22077. * responsiveness.
  22078. */
  22079. Chart.prototype.setResponsive = function(redraw) {
  22080. var options = this.options.responsive,
  22081. ruleIds = [],
  22082. currentResponsive = this.currentResponsive,
  22083. currentRuleIds;
  22084. if (options && options.rules) {
  22085. each(options.rules, function(rule) {
  22086. if (rule._id === undefined) {
  22087. rule._id = H.uniqueKey();
  22088. }
  22089. this.matchResponsiveRule(rule, ruleIds, redraw);
  22090. }, this);
  22091. }
  22092. // Merge matching rules
  22093. var mergedOptions = H.merge.apply(0, H.map(ruleIds, function(ruleId) {
  22094. return H.find(options.rules, function(rule) {
  22095. return rule._id === ruleId;
  22096. }).chartOptions;
  22097. }));
  22098. // Stringified key for the rules that currently apply.
  22099. ruleIds = ruleIds.toString() || undefined;
  22100. currentRuleIds = currentResponsive && currentResponsive.ruleIds;
  22101. // Changes in what rules apply
  22102. if (ruleIds !== currentRuleIds) {
  22103. // Undo previous rules. Before we apply a new set of rules, we need to
  22104. // roll back completely to base options (#6291).
  22105. if (currentResponsive) {
  22106. this.update(currentResponsive.undoOptions, redraw);
  22107. }
  22108. if (ruleIds) {
  22109. // Get undo-options for matching rules
  22110. this.currentResponsive = {
  22111. ruleIds: ruleIds,
  22112. mergedOptions: mergedOptions,
  22113. undoOptions: this.currentOptions(mergedOptions)
  22114. };
  22115. this.update(mergedOptions, redraw);
  22116. } else {
  22117. this.currentResponsive = undefined;
  22118. }
  22119. }
  22120. };
  22121. /**
  22122. * Handle a single responsiveness rule
  22123. */
  22124. Chart.prototype.matchResponsiveRule = function(rule, matches) {
  22125. var condition = rule.condition,
  22126. fn = condition.callback || function() {
  22127. return this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) &&
  22128. this.chartHeight <= pick(condition.maxHeight, Number.MAX_VALUE) &&
  22129. this.chartWidth >= pick(condition.minWidth, 0) &&
  22130. this.chartHeight >= pick(condition.minHeight, 0);
  22131. };
  22132. if (fn.call(this)) {
  22133. matches.push(rule._id);
  22134. }
  22135. };
  22136. /**
  22137. * Get the current values for a given set of options. Used before we update
  22138. * the chart with a new responsiveness rule.
  22139. * TODO: Restore axis options (by id?)
  22140. */
  22141. Chart.prototype.currentOptions = function(options) {
  22142. var ret = {};
  22143. /**
  22144. * Recurse over a set of options and its current values,
  22145. * and store the current values in the ret object.
  22146. */
  22147. function getCurrent(options, curr, ret, depth) {
  22148. var i;
  22149. H.objectEach(options, function(val, key) {
  22150. if (!depth && inArray(key, ['series', 'xAxis', 'yAxis']) > -1) {
  22151. options[key] = splat(options[key]);
  22152. ret[key] = [];
  22153. // Iterate over collections like series, xAxis or yAxis and map
  22154. // the items by index.
  22155. for (i = 0; i < options[key].length; i++) {
  22156. if (curr[key][i]) { // Item exists in current data (#6347)
  22157. ret[key][i] = {};
  22158. getCurrent(
  22159. val[i],
  22160. curr[key][i],
  22161. ret[key][i],
  22162. depth + 1
  22163. );
  22164. }
  22165. }
  22166. } else if (isObject(val)) {
  22167. ret[key] = isArray(val) ? [] : {};
  22168. getCurrent(val, curr[key] || {}, ret[key], depth + 1);
  22169. } else {
  22170. ret[key] = curr[key] || null;
  22171. }
  22172. });
  22173. }
  22174. getCurrent(options, this.options, ret, 0);
  22175. return ret;
  22176. };
  22177. }(Highcharts));
  22178. var Highcharts = (function(Highcharts) {
  22179. return Highcharts;
  22180. }(Highcharts));
  22181. (function(H) {
  22182. /**
  22183. * (c) 2010-2017 Torstein Honsi
  22184. *
  22185. * License: www.highcharts.com/license
  22186. */
  22187. var addEvent = H.addEvent,
  22188. Axis = H.Axis,
  22189. Chart = H.Chart,
  22190. css = H.css,
  22191. dateFormat = H.dateFormat,
  22192. defined = H.defined,
  22193. each = H.each,
  22194. extend = H.extend,
  22195. noop = H.noop,
  22196. Series = H.Series,
  22197. timeUnits = H.timeUnits,
  22198. wrap = H.wrap;
  22199. /* ****************************************************************************
  22200. * Start ordinal axis logic *
  22201. *****************************************************************************/
  22202. wrap(Series.prototype, 'init', function(proceed) {
  22203. var series = this,
  22204. xAxis;
  22205. // call the original function
  22206. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  22207. xAxis = series.xAxis;
  22208. // Destroy the extended ordinal index on updated data
  22209. if (xAxis && xAxis.options.ordinal) {
  22210. addEvent(series, 'updatedData', function() {
  22211. delete xAxis.ordinalIndex;
  22212. });
  22213. }
  22214. });
  22215. /**
  22216. * In an ordinal axis, there might be areas with dense consentrations of points, then large
  22217. * gaps between some. Creating equally distributed ticks over this entire range
  22218. * may lead to a huge number of ticks that will later be removed. So instead, break the
  22219. * positions up in segments, find the tick positions for each segment then concatenize them.
  22220. * This method is used from both data grouping logic and X axis tick position logic.
  22221. */
  22222. wrap(Axis.prototype, 'getTimeTicks', function(proceed, normalizedInterval, min, max, startOfWeek, positions, closestDistance, findHigherRanks) {
  22223. var start = 0,
  22224. end,
  22225. segmentPositions,
  22226. higherRanks = {},
  22227. hasCrossedHigherRank,
  22228. info,
  22229. posLength,
  22230. outsideMax,
  22231. groupPositions = [],
  22232. lastGroupPosition = -Number.MAX_VALUE,
  22233. tickPixelIntervalOption = this.options.tickPixelInterval;
  22234. // The positions are not always defined, for example for ordinal positions when data
  22235. // has regular interval (#1557, #2090)
  22236. if ((!this.options.ordinal && !this.options.breaks) || !positions || positions.length < 3 || min === undefined) {
  22237. return proceed.call(this, normalizedInterval, min, max, startOfWeek);
  22238. }
  22239. // Analyze the positions array to split it into segments on gaps larger than 5 times
  22240. // the closest distance. The closest distance is already found at this point, so
  22241. // we reuse that instead of computing it again.
  22242. posLength = positions.length;
  22243. for (end = 0; end < posLength; end++) {
  22244. outsideMax = end && positions[end - 1] > max;
  22245. if (positions[end] < min) { // Set the last position before min
  22246. start = end;
  22247. }
  22248. if (end === posLength - 1 || positions[end + 1] - positions[end] > closestDistance * 5 || outsideMax) {
  22249. // For each segment, calculate the tick positions from the getTimeTicks utility
  22250. // function. The interval will be the same regardless of how long the segment is.
  22251. if (positions[end] > lastGroupPosition) { // #1475
  22252. segmentPositions = proceed.call(this, normalizedInterval, positions[start], positions[end], startOfWeek);
  22253. // Prevent duplicate groups, for example for multiple segments within one larger time frame (#1475)
  22254. while (segmentPositions.length && segmentPositions[0] <= lastGroupPosition) {
  22255. segmentPositions.shift();
  22256. }
  22257. if (segmentPositions.length) {
  22258. lastGroupPosition = segmentPositions[segmentPositions.length - 1];
  22259. }
  22260. groupPositions = groupPositions.concat(segmentPositions);
  22261. }
  22262. // Set start of next segment
  22263. start = end + 1;
  22264. }
  22265. if (outsideMax) {
  22266. break;
  22267. }
  22268. }
  22269. // Get the grouping info from the last of the segments. The info is the same for
  22270. // all segments.
  22271. info = segmentPositions.info;
  22272. // Optionally identify ticks with higher rank, for example when the ticks
  22273. // have crossed midnight.
  22274. if (findHigherRanks && info.unitRange <= timeUnits.hour) {
  22275. end = groupPositions.length - 1;
  22276. // Compare points two by two
  22277. for (start = 1; start < end; start++) {
  22278. if (dateFormat('%d', groupPositions[start]) !== dateFormat('%d', groupPositions[start - 1])) {
  22279. higherRanks[groupPositions[start]] = 'day';
  22280. hasCrossedHigherRank = true;
  22281. }
  22282. }
  22283. // If the complete array has crossed midnight, we want to mark the first
  22284. // positions also as higher rank
  22285. if (hasCrossedHigherRank) {
  22286. higherRanks[groupPositions[0]] = 'day';
  22287. }
  22288. info.higherRanks = higherRanks;
  22289. }
  22290. // Save the info
  22291. groupPositions.info = info;
  22292. // Don't show ticks within a gap in the ordinal axis, where the space between
  22293. // two points is greater than a portion of the tick pixel interval
  22294. if (findHigherRanks && defined(tickPixelIntervalOption)) { // check for squashed ticks
  22295. var length = groupPositions.length,
  22296. i = length,
  22297. itemToRemove,
  22298. translated,
  22299. translatedArr = [],
  22300. lastTranslated,
  22301. medianDistance,
  22302. distance,
  22303. distances = [];
  22304. // Find median pixel distance in order to keep a reasonably even distance between
  22305. // ticks (#748)
  22306. while (i--) {
  22307. translated = this.translate(groupPositions[i]);
  22308. if (lastTranslated) {
  22309. distances[i] = lastTranslated - translated;
  22310. }
  22311. translatedArr[i] = lastTranslated = translated;
  22312. }
  22313. distances.sort();
  22314. medianDistance = distances[Math.floor(distances.length / 2)];
  22315. if (medianDistance < tickPixelIntervalOption * 0.6) {
  22316. medianDistance = null;
  22317. }
  22318. // Now loop over again and remove ticks where needed
  22319. i = groupPositions[length - 1] > max ? length - 1 : length; // #817
  22320. lastTranslated = undefined;
  22321. while (i--) {
  22322. translated = translatedArr[i];
  22323. distance = Math.abs(lastTranslated - translated);
  22324. // #4175 - when axis is reversed, the distance, is negative but
  22325. // tickPixelIntervalOption positive, so we need to compare the same values
  22326. // Remove ticks that are closer than 0.6 times the pixel interval from the one to the right,
  22327. // but not if it is close to the median distance (#748).
  22328. if (lastTranslated && distance < tickPixelIntervalOption * 0.8 &&
  22329. (medianDistance === null || distance < medianDistance * 0.8)) {
  22330. // Is this a higher ranked position with a normal position to the right?
  22331. if (higherRanks[groupPositions[i]] && !higherRanks[groupPositions[i + 1]]) {
  22332. // Yes: remove the lower ranked neighbour to the right
  22333. itemToRemove = i + 1;
  22334. lastTranslated = translated; // #709
  22335. } else {
  22336. // No: remove this one
  22337. itemToRemove = i;
  22338. }
  22339. groupPositions.splice(itemToRemove, 1);
  22340. } else {
  22341. lastTranslated = translated;
  22342. }
  22343. }
  22344. }
  22345. return groupPositions;
  22346. });
  22347. // Extend the Axis prototype
  22348. extend(Axis.prototype, /** @lends Axis.prototype */ {
  22349. /**
  22350. * Calculate the ordinal positions before tick positions are calculated.
  22351. */
  22352. beforeSetTickPositions: function() {
  22353. var axis = this,
  22354. len,
  22355. ordinalPositions = [],
  22356. useOrdinal = false,
  22357. dist,
  22358. extremes = axis.getExtremes(),
  22359. min = extremes.min,
  22360. max = extremes.max,
  22361. minIndex,
  22362. maxIndex,
  22363. slope,
  22364. hasBreaks = axis.isXAxis && !!axis.options.breaks,
  22365. isOrdinal = axis.options.ordinal,
  22366. ignoreHiddenSeries = axis.chart.options.chart.ignoreHiddenSeries,
  22367. i;
  22368. // apply the ordinal logic
  22369. if (isOrdinal || hasBreaks) { // #4167 YAxis is never ordinal ?
  22370. each(axis.series, function(series, i) {
  22371. if ((!ignoreHiddenSeries || series.visible !== false) && (series.takeOrdinalPosition !== false || hasBreaks)) {
  22372. // concatenate the processed X data into the existing positions, or the empty array
  22373. ordinalPositions = ordinalPositions.concat(series.processedXData);
  22374. len = ordinalPositions.length;
  22375. // remove duplicates (#1588)
  22376. ordinalPositions.sort(function(a, b) {
  22377. return a - b; // without a custom function it is sorted as strings
  22378. });
  22379. if (len) {
  22380. i = len - 1;
  22381. while (i--) {
  22382. if (ordinalPositions[i] === ordinalPositions[i + 1]) {
  22383. ordinalPositions.splice(i, 1);
  22384. }
  22385. }
  22386. }
  22387. }
  22388. });
  22389. // cache the length
  22390. len = ordinalPositions.length;
  22391. // Check if we really need the overhead of mapping axis data against the ordinal positions.
  22392. // If the series consist of evenly spaced data any way, we don't need any ordinal logic.
  22393. if (len > 2) { // two points have equal distance by default
  22394. dist = ordinalPositions[1] - ordinalPositions[0];
  22395. i = len - 1;
  22396. while (i-- && !useOrdinal) {
  22397. if (ordinalPositions[i + 1] - ordinalPositions[i] !== dist) {
  22398. useOrdinal = true;
  22399. }
  22400. }
  22401. // When zooming in on a week, prevent axis padding for weekends even though the data within
  22402. // the week is evenly spaced.
  22403. if (!axis.options.keepOrdinalPadding && (ordinalPositions[0] - min > dist || max - ordinalPositions[ordinalPositions.length - 1] > dist)) {
  22404. useOrdinal = true;
  22405. }
  22406. }
  22407. // Record the slope and offset to compute the linear values from the array index.
  22408. // Since the ordinal positions may exceed the current range, get the start and
  22409. // end positions within it (#719, #665b)
  22410. if (useOrdinal) {
  22411. // Register
  22412. axis.ordinalPositions = ordinalPositions;
  22413. // This relies on the ordinalPositions being set. Use Math.max
  22414. // and Math.min to prevent padding on either sides of the data.
  22415. minIndex = axis.ordinal2lin( // #5979
  22416. Math.max(
  22417. min,
  22418. ordinalPositions[0]
  22419. ),
  22420. true
  22421. );
  22422. maxIndex = Math.max(axis.ordinal2lin(
  22423. Math.min(
  22424. max,
  22425. ordinalPositions[ordinalPositions.length - 1]
  22426. ),
  22427. true
  22428. ), 1); // #3339
  22429. // Set the slope and offset of the values compared to the indices in the ordinal positions
  22430. axis.ordinalSlope = slope = (max - min) / (maxIndex - minIndex);
  22431. axis.ordinalOffset = min - (minIndex * slope);
  22432. } else {
  22433. axis.ordinalPositions = axis.ordinalSlope = axis.ordinalOffset = undefined;
  22434. }
  22435. }
  22436. axis.isOrdinal = isOrdinal && useOrdinal; // #3818, #4196, #4926
  22437. axis.groupIntervalFactor = null; // reset for next run
  22438. },
  22439. /**
  22440. * Translate from a linear axis value to the corresponding ordinal axis position. If there
  22441. * are no gaps in the ordinal axis this will be the same. The translated value is the value
  22442. * that the point would have if the axis were linear, using the same min and max.
  22443. *
  22444. * @param Number val The axis value
  22445. * @param Boolean toIndex Whether to return the index in the ordinalPositions or the new value
  22446. */
  22447. val2lin: function(val, toIndex) {
  22448. var axis = this,
  22449. ordinalPositions = axis.ordinalPositions,
  22450. ret;
  22451. if (!ordinalPositions) {
  22452. ret = val;
  22453. } else {
  22454. var ordinalLength = ordinalPositions.length,
  22455. i,
  22456. distance,
  22457. ordinalIndex;
  22458. // first look for an exact match in the ordinalpositions array
  22459. i = ordinalLength;
  22460. while (i--) {
  22461. if (ordinalPositions[i] === val) {
  22462. ordinalIndex = i;
  22463. break;
  22464. }
  22465. }
  22466. // if that failed, find the intermediate position between the two nearest values
  22467. i = ordinalLength - 1;
  22468. while (i--) {
  22469. if (val > ordinalPositions[i] || i === 0) { // interpolate
  22470. distance = (val - ordinalPositions[i]) / (ordinalPositions[i + 1] - ordinalPositions[i]); // something between 0 and 1
  22471. ordinalIndex = i + distance;
  22472. break;
  22473. }
  22474. }
  22475. ret = toIndex ?
  22476. ordinalIndex :
  22477. axis.ordinalSlope * (ordinalIndex || 0) + axis.ordinalOffset;
  22478. }
  22479. return ret;
  22480. },
  22481. /**
  22482. * Translate from linear (internal) to axis value
  22483. *
  22484. * @param Number val The linear abstracted value
  22485. * @param Boolean fromIndex Translate from an index in the ordinal positions rather than a value
  22486. */
  22487. lin2val: function(val, fromIndex) {
  22488. var axis = this,
  22489. ordinalPositions = axis.ordinalPositions,
  22490. ret;
  22491. if (!ordinalPositions) { // the visible range contains only equally spaced values
  22492. ret = val;
  22493. } else {
  22494. var ordinalSlope = axis.ordinalSlope,
  22495. ordinalOffset = axis.ordinalOffset,
  22496. i = ordinalPositions.length - 1,
  22497. linearEquivalentLeft,
  22498. linearEquivalentRight,
  22499. distance;
  22500. // Handle the case where we translate from the index directly, used only
  22501. // when panning an ordinal axis
  22502. if (fromIndex) {
  22503. if (val < 0) { // out of range, in effect panning to the left
  22504. val = ordinalPositions[0];
  22505. } else if (val > i) { // out of range, panning to the right
  22506. val = ordinalPositions[i];
  22507. } else { // split it up
  22508. i = Math.floor(val);
  22509. distance = val - i; // the decimal
  22510. }
  22511. // Loop down along the ordinal positions. When the linear equivalent of i matches
  22512. // an ordinal position, interpolate between the left and right values.
  22513. } else {
  22514. while (i--) {
  22515. linearEquivalentLeft = (ordinalSlope * i) + ordinalOffset;
  22516. if (val >= linearEquivalentLeft) {
  22517. linearEquivalentRight = (ordinalSlope * (i + 1)) + ordinalOffset;
  22518. distance = (val - linearEquivalentLeft) / (linearEquivalentRight - linearEquivalentLeft); // something between 0 and 1
  22519. break;
  22520. }
  22521. }
  22522. }
  22523. // If the index is within the range of the ordinal positions, return the associated
  22524. // or interpolated value. If not, just return the value
  22525. return distance !== undefined && ordinalPositions[i] !== undefined ?
  22526. ordinalPositions[i] + (distance ? distance * (ordinalPositions[i + 1] - ordinalPositions[i]) : 0) :
  22527. val;
  22528. }
  22529. return ret;
  22530. },
  22531. /**
  22532. * Get the ordinal positions for the entire data set. This is necessary in chart panning
  22533. * because we need to find out what points or data groups are available outside the
  22534. * visible range. When a panning operation starts, if an index for the given grouping
  22535. * does not exists, it is created and cached. This index is deleted on updated data, so
  22536. * it will be regenerated the next time a panning operation starts.
  22537. */
  22538. getExtendedPositions: function() {
  22539. var axis = this,
  22540. chart = axis.chart,
  22541. grouping = axis.series[0].currentDataGrouping,
  22542. ordinalIndex = axis.ordinalIndex,
  22543. key = grouping ? grouping.count + grouping.unitName : 'raw',
  22544. extremes = axis.getExtremes(),
  22545. fakeAxis,
  22546. fakeSeries;
  22547. // If this is the first time, or the ordinal index is deleted by updatedData,
  22548. // create it.
  22549. if (!ordinalIndex) {
  22550. ordinalIndex = axis.ordinalIndex = {};
  22551. }
  22552. if (!ordinalIndex[key]) {
  22553. // Create a fake axis object where the extended ordinal positions are emulated
  22554. fakeAxis = {
  22555. series: [],
  22556. chart: chart,
  22557. getExtremes: function() {
  22558. return {
  22559. min: extremes.dataMin,
  22560. max: extremes.dataMax
  22561. };
  22562. },
  22563. options: {
  22564. ordinal: true
  22565. },
  22566. val2lin: Axis.prototype.val2lin, // #2590
  22567. ordinal2lin: Axis.prototype.ordinal2lin // #6276
  22568. };
  22569. // Add the fake series to hold the full data, then apply processData to it
  22570. each(axis.series, function(series) {
  22571. fakeSeries = {
  22572. xAxis: fakeAxis,
  22573. xData: series.xData,
  22574. chart: chart,
  22575. destroyGroupedData: noop
  22576. };
  22577. fakeSeries.options = {
  22578. dataGrouping: grouping ? {
  22579. enabled: true,
  22580. forced: true,
  22581. approximation: 'open', // doesn't matter which, use the fastest
  22582. units: [
  22583. [grouping.unitName, [grouping.count]]
  22584. ]
  22585. } : {
  22586. enabled: false
  22587. }
  22588. };
  22589. series.processData.apply(fakeSeries);
  22590. fakeAxis.series.push(fakeSeries);
  22591. });
  22592. // Run beforeSetTickPositions to compute the ordinalPositions
  22593. axis.beforeSetTickPositions.apply(fakeAxis);
  22594. // Cache it
  22595. ordinalIndex[key] = fakeAxis.ordinalPositions;
  22596. }
  22597. return ordinalIndex[key];
  22598. },
  22599. /**
  22600. * Find the factor to estimate how wide the plot area would have been if ordinal
  22601. * gaps were included. This value is used to compute an imagined plot width in order
  22602. * to establish the data grouping interval.
  22603. *
  22604. * A real world case is the intraday-candlestick
  22605. * example. Without this logic, it would show the correct data grouping when viewing
  22606. * a range within each day, but once moving the range to include the gap between two
  22607. * days, the interval would include the cut-away night hours and the data grouping
  22608. * would be wrong. So the below method tries to compensate by identifying the most
  22609. * common point interval, in this case days.
  22610. *
  22611. * An opposite case is presented in issue #718. We have a long array of daily data,
  22612. * then one point is appended one hour after the last point. We expect the data grouping
  22613. * not to change.
  22614. *
  22615. * In the future, if we find cases where this estimation doesn't work optimally, we
  22616. * might need to add a second pass to the data grouping logic, where we do another run
  22617. * with a greater interval if the number of data groups is more than a certain fraction
  22618. * of the desired group count.
  22619. */
  22620. getGroupIntervalFactor: function(xMin, xMax, series) {
  22621. var i,
  22622. processedXData = series.processedXData,
  22623. len = processedXData.length,
  22624. distances = [],
  22625. median,
  22626. groupIntervalFactor = this.groupIntervalFactor;
  22627. // Only do this computation for the first series, let the other inherit it (#2416)
  22628. if (!groupIntervalFactor) {
  22629. // Register all the distances in an array
  22630. for (i = 0; i < len - 1; i++) {
  22631. distances[i] = processedXData[i + 1] - processedXData[i];
  22632. }
  22633. // Sort them and find the median
  22634. distances.sort(function(a, b) {
  22635. return a - b;
  22636. });
  22637. median = distances[Math.floor(len / 2)];
  22638. // Compensate for series that don't extend through the entire axis extent. #1675.
  22639. xMin = Math.max(xMin, processedXData[0]);
  22640. xMax = Math.min(xMax, processedXData[len - 1]);
  22641. this.groupIntervalFactor = groupIntervalFactor = (len * median) / (xMax - xMin);
  22642. }
  22643. // Return the factor needed for data grouping
  22644. return groupIntervalFactor;
  22645. },
  22646. /**
  22647. * Make the tick intervals closer because the ordinal gaps make the ticks spread out or cluster
  22648. */
  22649. postProcessTickInterval: function(tickInterval) {
  22650. // Problem: http://jsfiddle.net/highcharts/FQm4E/1/
  22651. // This is a case where this algorithm doesn't work optimally. In this case, the
  22652. // tick labels are spread out per week, but all the gaps reside within weeks. So
  22653. // we have a situation where the labels are courser than the ordinal gaps, and
  22654. // thus the tick interval should not be altered
  22655. var ordinalSlope = this.ordinalSlope,
  22656. ret;
  22657. if (ordinalSlope) {
  22658. if (!this.options.breaks) {
  22659. ret = tickInterval / (ordinalSlope / this.closestPointRange);
  22660. } else {
  22661. ret = this.closestPointRange;
  22662. }
  22663. } else {
  22664. ret = tickInterval;
  22665. }
  22666. return ret;
  22667. }
  22668. });
  22669. // Record this to prevent overwriting by broken-axis module (#5979)
  22670. Axis.prototype.ordinal2lin = Axis.prototype.val2lin;
  22671. // Extending the Chart.pan method for ordinal axes
  22672. wrap(Chart.prototype, 'pan', function(proceed, e) {
  22673. var chart = this,
  22674. xAxis = chart.xAxis[0],
  22675. chartX = e.chartX,
  22676. runBase = false;
  22677. if (xAxis.options.ordinal && xAxis.series.length) {
  22678. var mouseDownX = chart.mouseDownX,
  22679. extremes = xAxis.getExtremes(),
  22680. dataMax = extremes.dataMax,
  22681. min = extremes.min,
  22682. max = extremes.max,
  22683. trimmedRange,
  22684. hoverPoints = chart.hoverPoints,
  22685. closestPointRange = xAxis.closestPointRange,
  22686. pointPixelWidth = xAxis.translationSlope * (xAxis.ordinalSlope || closestPointRange),
  22687. movedUnits = (mouseDownX - chartX) / pointPixelWidth, // how many ordinal units did we move?
  22688. extendedAxis = {
  22689. ordinalPositions: xAxis.getExtendedPositions()
  22690. }, // get index of all the chart's points
  22691. ordinalPositions,
  22692. searchAxisLeft,
  22693. lin2val = xAxis.lin2val,
  22694. val2lin = xAxis.val2lin,
  22695. searchAxisRight;
  22696. if (!extendedAxis.ordinalPositions) { // we have an ordinal axis, but the data is equally spaced
  22697. runBase = true;
  22698. } else if (Math.abs(movedUnits) > 1) {
  22699. // Remove active points for shared tooltip
  22700. if (hoverPoints) {
  22701. each(hoverPoints, function(point) {
  22702. point.setState();
  22703. });
  22704. }
  22705. if (movedUnits < 0) {
  22706. searchAxisLeft = extendedAxis;
  22707. searchAxisRight = xAxis.ordinalPositions ? xAxis : extendedAxis;
  22708. } else {
  22709. searchAxisLeft = xAxis.ordinalPositions ? xAxis : extendedAxis;
  22710. searchAxisRight = extendedAxis;
  22711. }
  22712. // In grouped data series, the last ordinal position represents the grouped data, which is
  22713. // to the left of the real data max. If we don't compensate for this, we will be allowed
  22714. // to pan grouped data series passed the right of the plot area.
  22715. ordinalPositions = searchAxisRight.ordinalPositions;
  22716. if (dataMax > ordinalPositions[ordinalPositions.length - 1]) {
  22717. ordinalPositions.push(dataMax);
  22718. }
  22719. // Get the new min and max values by getting the ordinal index for the current extreme,
  22720. // then add the moved units and translate back to values. This happens on the
  22721. // extended ordinal positions if the new position is out of range, else it happens
  22722. // on the current x axis which is smaller and faster.
  22723. chart.fixedRange = max - min;
  22724. trimmedRange = xAxis.toFixedRange(null, null,
  22725. lin2val.apply(searchAxisLeft, [
  22726. val2lin.apply(searchAxisLeft, [min, true]) + movedUnits, // the new index
  22727. true // translate from index
  22728. ]),
  22729. lin2val.apply(searchAxisRight, [
  22730. val2lin.apply(searchAxisRight, [max, true]) + movedUnits, // the new index
  22731. true // translate from index
  22732. ])
  22733. );
  22734. // Apply it if it is within the available data range
  22735. if (trimmedRange.min >= Math.min(extremes.dataMin, min) && trimmedRange.max <= Math.max(dataMax, max)) {
  22736. xAxis.setExtremes(trimmedRange.min, trimmedRange.max, true, false, {
  22737. trigger: 'pan'
  22738. });
  22739. }
  22740. chart.mouseDownX = chartX; // set new reference for next run
  22741. css(chart.container, {
  22742. cursor: 'move'
  22743. });
  22744. }
  22745. } else {
  22746. runBase = true;
  22747. }
  22748. // revert to the linear chart.pan version
  22749. if (runBase) {
  22750. // call the original function
  22751. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  22752. }
  22753. });
  22754. /* ****************************************************************************
  22755. * End ordinal axis logic *
  22756. *****************************************************************************/
  22757. }(Highcharts));
  22758. (function(H) {
  22759. /**
  22760. * (c) 2009-2017 Torstein Honsi
  22761. *
  22762. * License: www.highcharts.com/license
  22763. */
  22764. var pick = H.pick,
  22765. wrap = H.wrap,
  22766. each = H.each,
  22767. extend = H.extend,
  22768. isArray = H.isArray,
  22769. fireEvent = H.fireEvent,
  22770. Axis = H.Axis,
  22771. Series = H.Series;
  22772. function stripArguments() {
  22773. return Array.prototype.slice.call(arguments, 1);
  22774. }
  22775. extend(Axis.prototype, {
  22776. isInBreak: function(brk, val) {
  22777. var ret,
  22778. repeat = brk.repeat || Infinity,
  22779. from = brk.from,
  22780. length = brk.to - brk.from,
  22781. test = (val >= from ? (val - from) % repeat : repeat - ((from - val) % repeat));
  22782. if (!brk.inclusive) {
  22783. ret = test < length && test !== 0;
  22784. } else {
  22785. ret = test <= length;
  22786. }
  22787. return ret;
  22788. },
  22789. isInAnyBreak: function(val, testKeep) {
  22790. var breaks = this.options.breaks,
  22791. i = breaks && breaks.length,
  22792. inbrk,
  22793. keep,
  22794. ret;
  22795. if (i) {
  22796. while (i--) {
  22797. if (this.isInBreak(breaks[i], val)) {
  22798. inbrk = true;
  22799. if (!keep) {
  22800. keep = pick(breaks[i].showPoints, this.isXAxis ? false : true);
  22801. }
  22802. }
  22803. }
  22804. if (inbrk && testKeep) {
  22805. ret = inbrk && !keep;
  22806. } else {
  22807. ret = inbrk;
  22808. }
  22809. }
  22810. return ret;
  22811. }
  22812. });
  22813. wrap(Axis.prototype, 'setTickPositions', function(proceed) {
  22814. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  22815. if (this.options.breaks) {
  22816. var axis = this,
  22817. tickPositions = this.tickPositions,
  22818. info = this.tickPositions.info,
  22819. newPositions = [],
  22820. i;
  22821. for (i = 0; i < tickPositions.length; i++) {
  22822. if (!axis.isInAnyBreak(tickPositions[i])) {
  22823. newPositions.push(tickPositions[i]);
  22824. }
  22825. }
  22826. this.tickPositions = newPositions;
  22827. this.tickPositions.info = info;
  22828. }
  22829. });
  22830. wrap(Axis.prototype, 'init', function(proceed, chart, userOptions) {
  22831. var axis = this,
  22832. breaks;
  22833. // Force Axis to be not-ordinal when breaks are defined
  22834. if (userOptions.breaks && userOptions.breaks.length) {
  22835. userOptions.ordinal = false;
  22836. }
  22837. proceed.call(this, chart, userOptions);
  22838. breaks = this.options.breaks;
  22839. axis.isBroken = (isArray(breaks) && !!breaks.length);
  22840. if (axis.isBroken) {
  22841. axis.val2lin = function(val) {
  22842. var nval = val,
  22843. brk,
  22844. i;
  22845. for (i = 0; i < axis.breakArray.length; i++) {
  22846. brk = axis.breakArray[i];
  22847. if (brk.to <= val) {
  22848. nval -= brk.len;
  22849. } else if (brk.from >= val) {
  22850. break;
  22851. } else if (axis.isInBreak(brk, val)) {
  22852. nval -= (val - brk.from);
  22853. break;
  22854. }
  22855. }
  22856. return nval;
  22857. };
  22858. axis.lin2val = function(val) {
  22859. var nval = val,
  22860. brk,
  22861. i;
  22862. for (i = 0; i < axis.breakArray.length; i++) {
  22863. brk = axis.breakArray[i];
  22864. if (brk.from >= nval) {
  22865. break;
  22866. } else if (brk.to < nval) {
  22867. nval += brk.len;
  22868. } else if (axis.isInBreak(brk, nval)) {
  22869. nval += brk.len;
  22870. }
  22871. }
  22872. return nval;
  22873. };
  22874. axis.setExtremes = function(newMin, newMax, redraw, animation, eventArguments) {
  22875. // If trying to set extremes inside a break, extend it to before and after the break ( #3857 )
  22876. while (this.isInAnyBreak(newMin)) {
  22877. newMin -= this.closestPointRange;
  22878. }
  22879. while (this.isInAnyBreak(newMax)) {
  22880. newMax -= this.closestPointRange;
  22881. }
  22882. Axis.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments);
  22883. };
  22884. axis.setAxisTranslation = function(saveOld) {
  22885. Axis.prototype.setAxisTranslation.call(this, saveOld);
  22886. var breaks = axis.options.breaks,
  22887. breakArrayT = [], // Temporary one
  22888. breakArray = [],
  22889. length = 0,
  22890. inBrk,
  22891. repeat,
  22892. min = axis.userMin || axis.min,
  22893. max = axis.userMax || axis.max,
  22894. pointRangePadding = pick(axis.pointRangePadding, 0),
  22895. start,
  22896. i;
  22897. // Min & max check (#4247)
  22898. each(breaks, function(brk) {
  22899. repeat = brk.repeat || Infinity;
  22900. if (axis.isInBreak(brk, min)) {
  22901. min += (brk.to % repeat) - (min % repeat);
  22902. }
  22903. if (axis.isInBreak(brk, max)) {
  22904. max -= (max % repeat) - (brk.from % repeat);
  22905. }
  22906. });
  22907. // Construct an array holding all breaks in the axis
  22908. each(breaks, function(brk) {
  22909. start = brk.from;
  22910. repeat = brk.repeat || Infinity;
  22911. while (start - repeat > min) {
  22912. start -= repeat;
  22913. }
  22914. while (start < min) {
  22915. start += repeat;
  22916. }
  22917. for (i = start; i < max; i += repeat) {
  22918. breakArrayT.push({
  22919. value: i,
  22920. move: 'in'
  22921. });
  22922. breakArrayT.push({
  22923. value: i + (brk.to - brk.from),
  22924. move: 'out',
  22925. size: brk.breakSize
  22926. });
  22927. }
  22928. });
  22929. breakArrayT.sort(function(a, b) {
  22930. var ret;
  22931. if (a.value === b.value) {
  22932. ret = (a.move === 'in' ? 0 : 1) - (b.move === 'in' ? 0 : 1);
  22933. } else {
  22934. ret = a.value - b.value;
  22935. }
  22936. return ret;
  22937. });
  22938. // Simplify the breaks
  22939. inBrk = 0;
  22940. start = min;
  22941. each(breakArrayT, function(brk) {
  22942. inBrk += (brk.move === 'in' ? 1 : -1);
  22943. if (inBrk === 1 && brk.move === 'in') {
  22944. start = brk.value;
  22945. }
  22946. if (inBrk === 0) {
  22947. breakArray.push({
  22948. from: start,
  22949. to: brk.value,
  22950. len: brk.value - start - (brk.size || 0)
  22951. });
  22952. length += brk.value - start - (brk.size || 0);
  22953. }
  22954. });
  22955. axis.breakArray = breakArray;
  22956. // Used with staticScale, and below, the actual axis length when
  22957. // breaks are substracted.
  22958. axis.unitLength = max - min - length + pointRangePadding;
  22959. fireEvent(axis, 'afterBreaks');
  22960. if (axis.options.staticScale) {
  22961. axis.transA = axis.options.staticScale;
  22962. } else if (axis.unitLength) {
  22963. axis.transA *= (max - axis.min + pointRangePadding) /
  22964. axis.unitLength;
  22965. }
  22966. if (pointRangePadding) {
  22967. axis.minPixelPadding = axis.transA * axis.minPointOffset;
  22968. }
  22969. axis.min = min;
  22970. axis.max = max;
  22971. };
  22972. }
  22973. });
  22974. wrap(Series.prototype, 'generatePoints', function(proceed) {
  22975. proceed.apply(this, stripArguments(arguments));
  22976. var series = this,
  22977. xAxis = series.xAxis,
  22978. yAxis = series.yAxis,
  22979. points = series.points,
  22980. point,
  22981. i = points.length,
  22982. connectNulls = series.options.connectNulls,
  22983. nullGap;
  22984. if (xAxis && yAxis && (xAxis.options.breaks || yAxis.options.breaks)) {
  22985. while (i--) {
  22986. point = points[i];
  22987. nullGap = point.y === null && connectNulls === false; // respect nulls inside the break (#4275)
  22988. if (!nullGap && (xAxis.isInAnyBreak(point.x, true) || yAxis.isInAnyBreak(point.y, true))) {
  22989. points.splice(i, 1);
  22990. if (this.data[i]) {
  22991. this.data[i].destroyElements(); // removes the graphics for this point if they exist
  22992. }
  22993. }
  22994. }
  22995. }
  22996. });
  22997. function drawPointsWrapped(proceed) {
  22998. proceed.apply(this);
  22999. this.drawBreaks(this.xAxis, ['x']);
  23000. this.drawBreaks(this.yAxis, pick(this.pointArrayMap, ['y']));
  23001. }
  23002. H.Series.prototype.drawBreaks = function(axis, keys) {
  23003. var series = this,
  23004. points = series.points,
  23005. breaks,
  23006. threshold,
  23007. eventName,
  23008. y;
  23009. if (!axis) {
  23010. return; // #5950
  23011. }
  23012. each(keys, function(key) {
  23013. breaks = axis.breakArray || [];
  23014. threshold = axis.isXAxis ? axis.min : pick(series.options.threshold, axis.min);
  23015. each(points, function(point) {
  23016. y = pick(point['stack' + key.toUpperCase()], point[key]);
  23017. each(breaks, function(brk) {
  23018. eventName = false;
  23019. if ((threshold < brk.from && y > brk.to) || (threshold > brk.from && y < brk.from)) {
  23020. eventName = 'pointBreak';
  23021. } else if ((threshold < brk.from && y > brk.from && y < brk.to) || (threshold > brk.from && y > brk.to && y < brk.from)) { // point falls inside the break
  23022. eventName = 'pointInBreak';
  23023. }
  23024. if (eventName) {
  23025. fireEvent(axis, eventName, {
  23026. point: point,
  23027. brk: brk
  23028. });
  23029. }
  23030. });
  23031. });
  23032. });
  23033. };
  23034. /**
  23035. * Extend getGraphPath by identifying gaps in the data so that we can draw a gap
  23036. * in the line or area. This was moved from ordinal axis module to broken axis
  23037. * module as of #5045.
  23038. */
  23039. H.Series.prototype.gappedPath = function() {
  23040. var gapSize = this.options.gapSize,
  23041. points = this.points.slice(),
  23042. i = points.length - 1;
  23043. if (gapSize && i > 0) { // #5008
  23044. // extension for ordinal breaks
  23045. while (i--) {
  23046. if (points[i + 1].x - points[i].x > this.closestPointRange * gapSize) {
  23047. points.splice( // insert after this one
  23048. i + 1,
  23049. 0, {
  23050. isNull: true
  23051. }
  23052. );
  23053. }
  23054. }
  23055. }
  23056. // Call base method
  23057. return this.getGraphPath(points);
  23058. };
  23059. wrap(H.seriesTypes.column.prototype, 'drawPoints', drawPointsWrapped);
  23060. wrap(H.Series.prototype, 'drawPoints', drawPointsWrapped);
  23061. }(Highcharts));
  23062. (function() {
  23063. }());
  23064. (function(H) {
  23065. /**
  23066. * (c) 2010-2017 Torstein Honsi
  23067. *
  23068. * License: www.highcharts.com/license
  23069. */
  23070. var arrayMax = H.arrayMax,
  23071. arrayMin = H.arrayMin,
  23072. Axis = H.Axis,
  23073. defaultPlotOptions = H.defaultPlotOptions,
  23074. defined = H.defined,
  23075. each = H.each,
  23076. extend = H.extend,
  23077. format = H.format,
  23078. isNumber = H.isNumber,
  23079. merge = H.merge,
  23080. pick = H.pick,
  23081. Point = H.Point,
  23082. Series = H.Series,
  23083. Tooltip = H.Tooltip,
  23084. wrap = H.wrap;
  23085. /* ****************************************************************************
  23086. * Start data grouping module *
  23087. ******************************************************************************/
  23088. var seriesProto = Series.prototype,
  23089. baseProcessData = seriesProto.processData,
  23090. baseGeneratePoints = seriesProto.generatePoints,
  23091. baseDestroy = seriesProto.destroy,
  23092. commonOptions = {
  23093. approximation: 'average', // average, open, high, low, close, sum
  23094. //enabled: null, // (true for stock charts, false for basic),
  23095. //forced: undefined,
  23096. groupPixelWidth: 2,
  23097. // the first one is the point or start value, the second is the start value if we're dealing with range,
  23098. // the third one is the end value if dealing with a range
  23099. dateTimeLabelFormats: {
  23100. millisecond: ['%A, %b %e, %H:%M:%S.%L', '%A, %b %e, %H:%M:%S.%L', '-%H:%M:%S.%L'],
  23101. second: ['%A, %b %e, %H:%M:%S', '%A, %b %e, %H:%M:%S', '-%H:%M:%S'],
  23102. minute: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
  23103. hour: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
  23104. day: ['%A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
  23105. week: ['Week from %A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
  23106. month: ['%B %Y', '%B', '-%B %Y'],
  23107. year: ['%Y', '%Y', '-%Y']
  23108. }
  23109. // smoothed = false, // enable this for navigator series only
  23110. },
  23111. specificOptions = { // extends common options
  23112. line: {},
  23113. spline: {},
  23114. area: {},
  23115. areaspline: {},
  23116. column: {
  23117. approximation: 'sum',
  23118. groupPixelWidth: 10
  23119. },
  23120. arearange: {
  23121. approximation: 'range'
  23122. },
  23123. areasplinerange: {
  23124. approximation: 'range'
  23125. },
  23126. columnrange: {
  23127. approximation: 'range',
  23128. groupPixelWidth: 10
  23129. },
  23130. candlestick: {
  23131. approximation: 'ohlc',
  23132. groupPixelWidth: 10
  23133. },
  23134. ohlc: {
  23135. approximation: 'ohlc',
  23136. groupPixelWidth: 5
  23137. }
  23138. },
  23139. // units are defined in a separate array to allow complete overriding in case of a user option
  23140. defaultDataGroupingUnits = H.defaultDataGroupingUnits = [
  23141. [
  23142. 'millisecond', // unit name
  23143. [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
  23144. ],
  23145. [
  23146. 'second', [1, 2, 5, 10, 15, 30]
  23147. ],
  23148. [
  23149. 'minute', [1, 2, 5, 10, 15, 30]
  23150. ],
  23151. [
  23152. 'hour', [1, 2, 3, 4, 6, 8, 12]
  23153. ],
  23154. [
  23155. 'day', [1]
  23156. ],
  23157. [
  23158. 'week', [1]
  23159. ],
  23160. [
  23161. 'month', [1, 3, 6]
  23162. ],
  23163. [
  23164. 'year',
  23165. null
  23166. ]
  23167. ],
  23168. /**
  23169. * Define the available approximation types. The data grouping
  23170. * approximations takes an array or numbers as the first parameter. In case
  23171. * of ohlc, four arrays are sent in as four parameters. Each array consists
  23172. * only of numbers. In case null values belong to the group, the property
  23173. * .hasNulls will be set to true on the array.
  23174. */
  23175. approximations = {
  23176. sum: function(arr) {
  23177. var len = arr.length,
  23178. ret;
  23179. // 1. it consists of nulls exclusively
  23180. if (!len && arr.hasNulls) {
  23181. ret = null;
  23182. // 2. it has a length and real values
  23183. } else if (len) {
  23184. ret = 0;
  23185. while (len--) {
  23186. ret += arr[len];
  23187. }
  23188. }
  23189. // 3. it has zero length, so just return undefined
  23190. // => doNothing()
  23191. return ret;
  23192. },
  23193. average: function(arr) {
  23194. var len = arr.length,
  23195. ret = approximations.sum(arr);
  23196. // If we have a number, return it divided by the length. If not,
  23197. // return null or undefined based on what the sum method finds.
  23198. if (isNumber(ret) && len) {
  23199. ret = ret / len;
  23200. }
  23201. return ret;
  23202. },
  23203. // The same as average, but for series with multiple values, like area
  23204. // ranges.
  23205. averages: function() { // #5479
  23206. var ret = [];
  23207. each(arguments, function(arr) {
  23208. ret.push(approximations.average(arr));
  23209. });
  23210. return ret;
  23211. },
  23212. open: function(arr) {
  23213. return arr.length ? arr[0] : (arr.hasNulls ? null : undefined);
  23214. },
  23215. high: function(arr) {
  23216. return arr.length ? arrayMax(arr) : (arr.hasNulls ? null : undefined);
  23217. },
  23218. low: function(arr) {
  23219. return arr.length ? arrayMin(arr) : (arr.hasNulls ? null : undefined);
  23220. },
  23221. close: function(arr) {
  23222. return arr.length ? arr[arr.length - 1] : (arr.hasNulls ? null : undefined);
  23223. },
  23224. // ohlc and range are special cases where a multidimensional array is input and an array is output
  23225. ohlc: function(open, high, low, close) {
  23226. open = approximations.open(open);
  23227. high = approximations.high(high);
  23228. low = approximations.low(low);
  23229. close = approximations.close(close);
  23230. if (isNumber(open) || isNumber(high) || isNumber(low) || isNumber(close)) {
  23231. return [open, high, low, close];
  23232. }
  23233. // else, return is undefined
  23234. },
  23235. range: function(low, high) {
  23236. low = approximations.low(low);
  23237. high = approximations.high(high);
  23238. if (isNumber(low) || isNumber(high)) {
  23239. return [low, high];
  23240. } else if (low === null && high === null) {
  23241. return null;
  23242. }
  23243. // else, return is undefined
  23244. }
  23245. };
  23246. /**
  23247. * Takes parallel arrays of x and y data and groups the data into intervals
  23248. * defined by groupPositions, a collection of starting x values for each group.
  23249. */
  23250. seriesProto.groupData = function(xData, yData, groupPositions, approximation) {
  23251. var series = this,
  23252. data = series.data,
  23253. dataOptions = series.options.data,
  23254. groupedXData = [],
  23255. groupedYData = [],
  23256. groupMap = [],
  23257. dataLength = xData.length,
  23258. pointX,
  23259. pointY,
  23260. groupedY,
  23261. // when grouping the fake extended axis for panning,
  23262. // we don't need to consider y
  23263. handleYData = !!yData,
  23264. values = [],
  23265. approximationFn = typeof approximation === 'function' ?
  23266. approximation :
  23267. approximations[approximation] ||
  23268. // if the approximation is not found use default series type
  23269. // approximation (#2914)
  23270. (
  23271. specificOptions[series.type] &&
  23272. approximations[specificOptions[series.type].approximation]
  23273. ) || approximations[commonOptions.approximation],
  23274. pointArrayMap = series.pointArrayMap,
  23275. pointArrayMapLength = pointArrayMap && pointArrayMap.length,
  23276. pos = 0,
  23277. start = 0,
  23278. valuesLen,
  23279. i, j;
  23280. // Calculate values array size from pointArrayMap length
  23281. if (pointArrayMapLength) {
  23282. each(pointArrayMap, function() {
  23283. values.push([]);
  23284. });
  23285. } else {
  23286. values.push([]);
  23287. }
  23288. valuesLen = pointArrayMapLength || 1;
  23289. // Start with the first point within the X axis range (#2696)
  23290. for (i = 0; i <= dataLength; i++) {
  23291. if (xData[i] >= groupPositions[0]) {
  23292. break;
  23293. }
  23294. }
  23295. for (i; i <= dataLength; i++) {
  23296. // when a new group is entered, summarize and initiate
  23297. // the previous group
  23298. while ((
  23299. groupPositions[pos + 1] !== undefined &&
  23300. xData[i] >= groupPositions[pos + 1]
  23301. ) || i === dataLength) { // get the last group
  23302. // get group x and y
  23303. pointX = groupPositions[pos];
  23304. series.dataGroupInfo = {
  23305. start: start,
  23306. length: values[0].length
  23307. };
  23308. groupedY = approximationFn.apply(series, values);
  23309. // push the grouped data
  23310. if (groupedY !== undefined) {
  23311. groupedXData.push(pointX);
  23312. groupedYData.push(groupedY);
  23313. groupMap.push(series.dataGroupInfo);
  23314. }
  23315. // reset the aggregate arrays
  23316. start = i;
  23317. for (j = 0; j < valuesLen; j++) {
  23318. values[j].length = 0; // faster than values[j] = []
  23319. values[j].hasNulls = false;
  23320. }
  23321. // Advance on the group positions
  23322. pos += 1;
  23323. // don't loop beyond the last group
  23324. if (i === dataLength) {
  23325. break;
  23326. }
  23327. }
  23328. // break out
  23329. if (i === dataLength) {
  23330. break;
  23331. }
  23332. // for each raw data point, push it to an array that contains all values
  23333. // for this specific group
  23334. if (pointArrayMap) {
  23335. var index = series.cropStart + i,
  23336. point = (data && data[index]) ||
  23337. series.pointClass.prototype.applyOptions.apply({
  23338. series: series
  23339. }, [dataOptions[index]]),
  23340. val;
  23341. for (j = 0; j < pointArrayMapLength; j++) {
  23342. val = point[pointArrayMap[j]];
  23343. if (isNumber(val)) {
  23344. values[j].push(val);
  23345. } else if (val === null) {
  23346. values[j].hasNulls = true;
  23347. }
  23348. }
  23349. } else {
  23350. pointY = handleYData ? yData[i] : null;
  23351. if (isNumber(pointY)) {
  23352. values[0].push(pointY);
  23353. } else if (pointY === null) {
  23354. values[0].hasNulls = true;
  23355. }
  23356. }
  23357. }
  23358. return [groupedXData, groupedYData, groupMap];
  23359. };
  23360. /**
  23361. * Extend the basic processData method, that crops the data to the current zoom
  23362. * range, with data grouping logic.
  23363. */
  23364. seriesProto.processData = function() {
  23365. var series = this,
  23366. chart = series.chart,
  23367. options = series.options,
  23368. dataGroupingOptions = options.dataGrouping,
  23369. groupingEnabled = series.allowDG !== false && dataGroupingOptions &&
  23370. pick(dataGroupingOptions.enabled, chart.options.isStock),
  23371. visible = series.visible || !chart.options.chart.ignoreHiddenSeries,
  23372. hasGroupedData,
  23373. skip;
  23374. // run base method
  23375. series.forceCrop = groupingEnabled; // #334
  23376. series.groupPixelWidth = null; // #2110
  23377. series.hasProcessed = true; // #2692
  23378. // skip if processData returns false or if grouping is disabled (in that order)
  23379. skip = baseProcessData.apply(series, arguments) === false || !groupingEnabled;
  23380. if (!skip) {
  23381. series.destroyGroupedData();
  23382. var i,
  23383. processedXData = series.processedXData,
  23384. processedYData = series.processedYData,
  23385. plotSizeX = chart.plotSizeX,
  23386. xAxis = series.xAxis,
  23387. ordinal = xAxis.options.ordinal,
  23388. groupPixelWidth = series.groupPixelWidth = xAxis.getGroupPixelWidth && xAxis.getGroupPixelWidth();
  23389. // Execute grouping if the amount of points is greater than the limit defined in groupPixelWidth
  23390. if (groupPixelWidth) {
  23391. hasGroupedData = true;
  23392. series.isDirty = true; // force recreation of point instances in series.translate, #5699
  23393. series.points = null; // #6709
  23394. var extremes = xAxis.getExtremes(),
  23395. xMin = extremes.min,
  23396. xMax = extremes.max,
  23397. groupIntervalFactor = (ordinal && xAxis.getGroupIntervalFactor(xMin, xMax, series)) || 1,
  23398. interval = (groupPixelWidth * (xMax - xMin) / plotSizeX) * groupIntervalFactor,
  23399. groupPositions = xAxis.getTimeTicks(
  23400. xAxis.normalizeTimeTickInterval(interval, dataGroupingOptions.units || defaultDataGroupingUnits),
  23401. Math.min(xMin, processedXData[0]), // Processed data may extend beyond axis (#4907)
  23402. Math.max(xMax, processedXData[processedXData.length - 1]),
  23403. xAxis.options.startOfWeek,
  23404. processedXData,
  23405. series.closestPointRange
  23406. ),
  23407. groupedData = seriesProto.groupData.apply(series, [processedXData, processedYData, groupPositions, dataGroupingOptions.approximation]),
  23408. groupedXData = groupedData[0],
  23409. groupedYData = groupedData[1];
  23410. // prevent the smoothed data to spill out left and right, and make
  23411. // sure data is not shifted to the left
  23412. if (dataGroupingOptions.smoothed) {
  23413. i = groupedXData.length - 1;
  23414. groupedXData[i] = Math.min(groupedXData[i], xMax);
  23415. while (i-- && i > 0) {
  23416. groupedXData[i] += interval / 2;
  23417. }
  23418. groupedXData[0] = Math.max(groupedXData[0], xMin);
  23419. }
  23420. // record what data grouping values were used
  23421. series.currentDataGrouping = groupPositions.info;
  23422. series.closestPointRange = groupPositions.info.totalRange;
  23423. series.groupMap = groupedData[2];
  23424. // Make sure the X axis extends to show the first group (#2533)
  23425. // But only for visible series (#5493, #6393)
  23426. if (defined(groupedXData[0]) && groupedXData[0] < xAxis.dataMin && visible) {
  23427. if (xAxis.min === xAxis.dataMin) {
  23428. xAxis.min = groupedXData[0];
  23429. }
  23430. xAxis.dataMin = groupedXData[0];
  23431. }
  23432. // set series props
  23433. series.processedXData = groupedXData;
  23434. series.processedYData = groupedYData;
  23435. } else {
  23436. series.currentDataGrouping = series.groupMap = null;
  23437. }
  23438. series.hasGroupedData = hasGroupedData;
  23439. }
  23440. };
  23441. /**
  23442. * Destroy the grouped data points. #622, #740
  23443. */
  23444. seriesProto.destroyGroupedData = function() {
  23445. var groupedData = this.groupedData;
  23446. // clear previous groups
  23447. each(groupedData || [], function(point, i) {
  23448. if (point) {
  23449. groupedData[i] = point.destroy ? point.destroy() : null;
  23450. }
  23451. });
  23452. this.groupedData = null;
  23453. };
  23454. /**
  23455. * Override the generatePoints method by adding a reference to grouped data
  23456. */
  23457. seriesProto.generatePoints = function() {
  23458. baseGeneratePoints.apply(this);
  23459. // record grouped data in order to let it be destroyed the next time processData runs
  23460. this.destroyGroupedData(); // #622
  23461. this.groupedData = this.hasGroupedData ? this.points : null;
  23462. };
  23463. /**
  23464. * Override point prototype to throw a warning when trying to update grouped points
  23465. */
  23466. wrap(Point.prototype, 'update', function(proceed) {
  23467. if (this.dataGroup) {
  23468. H.error(24);
  23469. } else {
  23470. proceed.apply(this, [].slice.call(arguments, 1));
  23471. }
  23472. });
  23473. /**
  23474. * Extend the original method, make the tooltip's header reflect the grouped range
  23475. */
  23476. wrap(Tooltip.prototype, 'tooltipFooterHeaderFormatter', function(proceed, labelConfig, isFooter) {
  23477. var tooltip = this,
  23478. series = labelConfig.series,
  23479. options = series.options,
  23480. tooltipOptions = series.tooltipOptions,
  23481. dataGroupingOptions = options.dataGrouping,
  23482. xDateFormat = tooltipOptions.xDateFormat,
  23483. xDateFormatEnd,
  23484. xAxis = series.xAxis,
  23485. dateFormat = H.dateFormat,
  23486. currentDataGrouping,
  23487. dateTimeLabelFormats,
  23488. labelFormats,
  23489. formattedKey;
  23490. // apply only to grouped series
  23491. if (xAxis && xAxis.options.type === 'datetime' && dataGroupingOptions && isNumber(labelConfig.key)) {
  23492. // set variables
  23493. currentDataGrouping = series.currentDataGrouping;
  23494. dateTimeLabelFormats = dataGroupingOptions.dateTimeLabelFormats;
  23495. // if we have grouped data, use the grouping information to get the right format
  23496. if (currentDataGrouping) {
  23497. labelFormats = dateTimeLabelFormats[currentDataGrouping.unitName];
  23498. if (currentDataGrouping.count === 1) {
  23499. xDateFormat = labelFormats[0];
  23500. } else {
  23501. xDateFormat = labelFormats[1];
  23502. xDateFormatEnd = labelFormats[2];
  23503. }
  23504. // if not grouped, and we don't have set the xDateFormat option, get the best fit,
  23505. // so if the least distance between points is one minute, show it, but if the
  23506. // least distance is one day, skip hours and minutes etc.
  23507. } else if (!xDateFormat && dateTimeLabelFormats) {
  23508. xDateFormat = tooltip.getXDateFormat(labelConfig, tooltipOptions, xAxis);
  23509. }
  23510. // now format the key
  23511. formattedKey = dateFormat(xDateFormat, labelConfig.key);
  23512. if (xDateFormatEnd) {
  23513. formattedKey += dateFormat(xDateFormatEnd, labelConfig.key + currentDataGrouping.totalRange - 1);
  23514. }
  23515. // return the replaced format
  23516. return format(tooltipOptions[(isFooter ? 'footer' : 'header') + 'Format'], {
  23517. point: extend(labelConfig.point, {
  23518. key: formattedKey
  23519. }),
  23520. series: series
  23521. });
  23522. }
  23523. // else, fall back to the regular formatter
  23524. return proceed.call(tooltip, labelConfig, isFooter);
  23525. });
  23526. /**
  23527. * Extend the series destroyer
  23528. */
  23529. seriesProto.destroy = function() {
  23530. var series = this,
  23531. groupedData = series.groupedData || [],
  23532. i = groupedData.length;
  23533. while (i--) {
  23534. if (groupedData[i]) {
  23535. groupedData[i].destroy();
  23536. }
  23537. }
  23538. baseDestroy.apply(series);
  23539. };
  23540. // Handle default options for data grouping. This must be set at runtime because some series types are
  23541. // defined after this.
  23542. wrap(seriesProto, 'setOptions', function(proceed, itemOptions) {
  23543. var options = proceed.call(this, itemOptions),
  23544. type = this.type,
  23545. plotOptions = this.chart.options.plotOptions,
  23546. defaultOptions = defaultPlotOptions[type].dataGrouping;
  23547. if (specificOptions[type]) { // #1284
  23548. if (!defaultOptions) {
  23549. defaultOptions = merge(commonOptions, specificOptions[type]);
  23550. }
  23551. options.dataGrouping = merge(
  23552. defaultOptions,
  23553. plotOptions.series && plotOptions.series.dataGrouping, // #1228
  23554. plotOptions[type].dataGrouping, // Set by the StockChart constructor
  23555. itemOptions.dataGrouping
  23556. );
  23557. }
  23558. if (this.chart.options.isStock) {
  23559. this.requireSorting = true;
  23560. }
  23561. return options;
  23562. });
  23563. /**
  23564. * When resetting the scale reset the hasProccessed flag to avoid taking previous data grouping
  23565. * of neighbour series into accound when determining group pixel width (#2692).
  23566. */
  23567. wrap(Axis.prototype, 'setScale', function(proceed) {
  23568. proceed.call(this);
  23569. each(this.series, function(series) {
  23570. series.hasProcessed = false;
  23571. });
  23572. });
  23573. /**
  23574. * Get the data grouping pixel width based on the greatest defined individual width
  23575. * of the axis' series, and if whether one of the axes need grouping.
  23576. */
  23577. Axis.prototype.getGroupPixelWidth = function() {
  23578. var series = this.series,
  23579. len = series.length,
  23580. i,
  23581. groupPixelWidth = 0,
  23582. doGrouping = false,
  23583. dataLength,
  23584. dgOptions;
  23585. // If multiple series are compared on the same x axis, give them the same
  23586. // group pixel width (#334)
  23587. i = len;
  23588. while (i--) {
  23589. dgOptions = series[i].options.dataGrouping;
  23590. if (dgOptions) {
  23591. groupPixelWidth = Math.max(groupPixelWidth, dgOptions.groupPixelWidth);
  23592. }
  23593. }
  23594. // If one of the series needs grouping, apply it to all (#1634)
  23595. i = len;
  23596. while (i--) {
  23597. dgOptions = series[i].options.dataGrouping;
  23598. if (dgOptions && series[i].hasProcessed) { // #2692
  23599. dataLength = (series[i].processedXData || series[i].data).length;
  23600. // Execute grouping if the amount of points is greater than the limit defined in groupPixelWidth
  23601. if (series[i].groupPixelWidth || dataLength > (this.chart.plotSizeX / groupPixelWidth) || (dataLength && dgOptions.forced)) {
  23602. doGrouping = true;
  23603. }
  23604. }
  23605. }
  23606. return doGrouping ? groupPixelWidth : 0;
  23607. };
  23608. /**
  23609. * Highstock only. Force data grouping on all the axis' series.
  23610. *
  23611. * @param {SeriesDatagroupingOptions} [dataGrouping]
  23612. * A `dataGrouping` configuration. Use `false` to disable data grouping
  23613. * dynamically.
  23614. * @param {Boolean} [redraw=true]
  23615. * Whether to redraw the chart or wait for a later call to {@link
  23616. * Chart#redraw}.
  23617. *
  23618. * @function setDataGrouping
  23619. * @memberOf Axis.prototype
  23620. */
  23621. Axis.prototype.setDataGrouping = function(dataGrouping, redraw) {
  23622. var i;
  23623. redraw = pick(redraw, true);
  23624. if (!dataGrouping) {
  23625. dataGrouping = {
  23626. forced: false,
  23627. units: null
  23628. };
  23629. }
  23630. // Axis is instantiated, update all series
  23631. if (this instanceof Axis) {
  23632. i = this.series.length;
  23633. while (i--) {
  23634. this.series[i].update({
  23635. dataGrouping: dataGrouping
  23636. }, false);
  23637. }
  23638. // Axis not yet instanciated, alter series options
  23639. } else {
  23640. each(this.chart.options.series, function(seriesOptions) {
  23641. seriesOptions.dataGrouping = dataGrouping;
  23642. }, false);
  23643. }
  23644. if (redraw) {
  23645. this.chart.redraw();
  23646. }
  23647. };
  23648. /* ****************************************************************************
  23649. * End data grouping module *
  23650. ******************************************************************************/
  23651. }(Highcharts));
  23652. (function(H) {
  23653. /**
  23654. * (c) 2010-2017 Torstein Honsi
  23655. *
  23656. * License: www.highcharts.com/license
  23657. */
  23658. var each = H.each,
  23659. Point = H.Point,
  23660. seriesType = H.seriesType,
  23661. seriesTypes = H.seriesTypes;
  23662. /**
  23663. * The ohlc series type.
  23664. *
  23665. * @constructor seriesTypes.ohlc
  23666. * @augments seriesTypes.column
  23667. */
  23668. seriesType('ohlc', 'column', {
  23669. lineWidth: 1,
  23670. tooltip: {
  23671. pointFormat: '<span style="color:{point.color}">\u25CF</span> <b> {series.name}</b><br/>' +
  23672. 'Open: {point.open}<br/>' +
  23673. 'High: {point.high}<br/>' +
  23674. 'Low: {point.low}<br/>' +
  23675. 'Close: {point.close}<br/>'
  23676. },
  23677. threshold: null,
  23678. states: {
  23679. hover: {
  23680. lineWidth: 3
  23681. }
  23682. },
  23683. stickyTracking: true
  23684. //upColor: undefined
  23685. }, /** @lends seriesTypes.ohlc */ {
  23686. directTouch: false,
  23687. pointArrayMap: ['open', 'high', 'low', 'close'], // array point configs are mapped to this
  23688. toYData: function(point) { // return a plain array for speedy calculation
  23689. return [point.open, point.high, point.low, point.close];
  23690. },
  23691. pointValKey: 'close',
  23692. pointAttrToOptions: {
  23693. 'stroke': 'color',
  23694. 'stroke-width': 'lineWidth'
  23695. },
  23696. /**
  23697. * Postprocess mapping between options and SVG attributes
  23698. */
  23699. pointAttribs: function(point, state) {
  23700. var attribs = seriesTypes.column.prototype.pointAttribs.call(
  23701. this,
  23702. point,
  23703. state
  23704. ),
  23705. options = this.options;
  23706. delete attribs.fill;
  23707. if (!point.options.color &&
  23708. options.upColor &&
  23709. point.open < point.close
  23710. ) {
  23711. attribs.stroke = options.upColor;
  23712. }
  23713. return attribs;
  23714. },
  23715. /**
  23716. * Translate data points from raw values x and y to plotX and plotY
  23717. */
  23718. translate: function() {
  23719. var series = this,
  23720. yAxis = series.yAxis,
  23721. hasModifyValue = !!series.modifyValue,
  23722. translated = ['plotOpen', 'plotHigh', 'plotLow', 'plotClose', 'yBottom']; // translate OHLC for
  23723. seriesTypes.column.prototype.translate.apply(series);
  23724. // Do the translation
  23725. each(series.points, function(point) {
  23726. each([point.open, point.high, point.low, point.close, point.low], function(value, i) {
  23727. if (value !== null) {
  23728. if (hasModifyValue) {
  23729. value = series.modifyValue(value);
  23730. }
  23731. point[translated[i]] = yAxis.toPixels(value, true);
  23732. }
  23733. });
  23734. // Align the tooltip to the high value to avoid covering the point
  23735. point.tooltipPos[1] =
  23736. point.plotHigh + yAxis.pos - series.chart.plotTop;
  23737. });
  23738. },
  23739. /**
  23740. * Draw the data points
  23741. */
  23742. drawPoints: function() {
  23743. var series = this,
  23744. points = series.points,
  23745. chart = series.chart;
  23746. each(points, function(point) {
  23747. var plotOpen,
  23748. plotClose,
  23749. crispCorr,
  23750. halfWidth,
  23751. path,
  23752. graphic = point.graphic,
  23753. crispX,
  23754. isNew = !graphic;
  23755. if (point.plotY !== undefined) {
  23756. // Create and/or update the graphic
  23757. if (!graphic) {
  23758. point.graphic = graphic = chart.renderer.path()
  23759. .add(series.group);
  23760. }
  23761. graphic.attr(series.pointAttribs(point, point.selected && 'select')); // #3897
  23762. // crisp vector coordinates
  23763. crispCorr = (graphic.strokeWidth() % 2) / 2;
  23764. crispX = Math.round(point.plotX) - crispCorr; // #2596
  23765. halfWidth = Math.round(point.shapeArgs.width / 2);
  23766. // the vertical stem
  23767. path = [
  23768. 'M',
  23769. crispX, Math.round(point.yBottom),
  23770. 'L',
  23771. crispX, Math.round(point.plotHigh)
  23772. ];
  23773. // open
  23774. if (point.open !== null) {
  23775. plotOpen = Math.round(point.plotOpen) + crispCorr;
  23776. path.push(
  23777. 'M',
  23778. crispX,
  23779. plotOpen,
  23780. 'L',
  23781. crispX - halfWidth,
  23782. plotOpen
  23783. );
  23784. }
  23785. // close
  23786. if (point.close !== null) {
  23787. plotClose = Math.round(point.plotClose) + crispCorr;
  23788. path.push(
  23789. 'M',
  23790. crispX,
  23791. plotClose,
  23792. 'L',
  23793. crispX + halfWidth,
  23794. plotClose
  23795. );
  23796. }
  23797. graphic[isNew ? 'attr' : 'animate']({
  23798. d: path
  23799. })
  23800. .addClass(point.getClassName(), true);
  23801. }
  23802. });
  23803. },
  23804. animate: null // Disable animation
  23805. /**
  23806. * @constructor seriesTypes.ohlc.prototype.pointClass
  23807. * @extends {Point}
  23808. */
  23809. }, /** @lends seriesTypes.ohlc.prototype.pointClass.prototype */ {
  23810. /**
  23811. * Extend the parent method by adding up or down to the class name.
  23812. */
  23813. getClassName: function() {
  23814. return Point.prototype.getClassName.call(this) +
  23815. (this.open < this.close ? ' highcharts-point-up' : ' highcharts-point-down');
  23816. }
  23817. });
  23818. /* ****************************************************************************
  23819. * End OHLC series code *
  23820. *****************************************************************************/
  23821. }(Highcharts));
  23822. (function(H) {
  23823. /**
  23824. * (c) 2010-2017 Torstein Honsi
  23825. *
  23826. * License: www.highcharts.com/license
  23827. */
  23828. var defaultPlotOptions = H.defaultPlotOptions,
  23829. each = H.each,
  23830. merge = H.merge,
  23831. seriesType = H.seriesType,
  23832. seriesTypes = H.seriesTypes;
  23833. /**
  23834. * The candlestick series type.
  23835. *
  23836. * @constructor seriesTypes.candlestick
  23837. * @augments seriesTypes.ohlc
  23838. */
  23839. seriesType('candlestick', 'ohlc', merge(defaultPlotOptions.column, {
  23840. states: {
  23841. hover: {
  23842. lineWidth: 2
  23843. }
  23844. },
  23845. tooltip: defaultPlotOptions.ohlc.tooltip,
  23846. threshold: null,
  23847. lineColor: '#000000',
  23848. lineWidth: 1,
  23849. upColor: '#ffffff',
  23850. stickyTracking: true
  23851. // upLineColor: null
  23852. }), /** @lends seriesTypes.candlestick */ {
  23853. /**
  23854. * Postprocess mapping between options and SVG attributes
  23855. */
  23856. pointAttribs: function(point, state) {
  23857. var attribs = seriesTypes.column.prototype.pointAttribs.call(this, point, state),
  23858. options = this.options,
  23859. isUp = point.open < point.close,
  23860. stroke = options.lineColor || this.color,
  23861. stateOptions;
  23862. attribs['stroke-width'] = options.lineWidth;
  23863. attribs.fill = point.options.color || (isUp ? (options.upColor || this.color) : this.color);
  23864. attribs.stroke = point.lineColor || (isUp ? (options.upLineColor || stroke) : stroke);
  23865. // Select or hover states
  23866. if (state) {
  23867. stateOptions = options.states[state];
  23868. attribs.fill = stateOptions.color || attribs.fill;
  23869. attribs.stroke = stateOptions.lineColor || attribs.stroke;
  23870. attribs['stroke-width'] =
  23871. stateOptions.lineWidth || attribs['stroke-width'];
  23872. }
  23873. return attribs;
  23874. },
  23875. /**
  23876. * Draw the data points
  23877. */
  23878. drawPoints: function() {
  23879. var series = this, //state = series.state,
  23880. points = series.points,
  23881. chart = series.chart;
  23882. each(points, function(point) {
  23883. var graphic = point.graphic,
  23884. plotOpen,
  23885. plotClose,
  23886. topBox,
  23887. bottomBox,
  23888. hasTopWhisker,
  23889. hasBottomWhisker,
  23890. crispCorr,
  23891. crispX,
  23892. path,
  23893. halfWidth,
  23894. isNew = !graphic;
  23895. if (point.plotY !== undefined) {
  23896. if (!graphic) {
  23897. point.graphic = graphic = chart.renderer.path()
  23898. .add(series.group);
  23899. }
  23900. graphic
  23901. .attr(series.pointAttribs(point, point.selected && 'select')) // #3897
  23902. .shadow(series.options.shadow);
  23903. // Crisp vector coordinates
  23904. crispCorr = (graphic.strokeWidth() % 2) / 2;
  23905. crispX = Math.round(point.plotX) - crispCorr; // #2596
  23906. plotOpen = point.plotOpen;
  23907. plotClose = point.plotClose;
  23908. topBox = Math.min(plotOpen, plotClose);
  23909. bottomBox = Math.max(plotOpen, plotClose);
  23910. halfWidth = Math.round(point.shapeArgs.width / 2);
  23911. hasTopWhisker = Math.round(topBox) !== Math.round(point.plotHigh);
  23912. hasBottomWhisker = bottomBox !== point.yBottom;
  23913. topBox = Math.round(topBox) + crispCorr;
  23914. bottomBox = Math.round(bottomBox) + crispCorr;
  23915. // Create the path. Due to a bug in Chrome 49, the path is first instanciated
  23916. // with no values, then the values pushed. For unknown reasons, instanciated
  23917. // the path array with all the values would lead to a crash when updating
  23918. // frequently (#5193).
  23919. path = [];
  23920. path.push(
  23921. 'M',
  23922. crispX - halfWidth, bottomBox,
  23923. 'L',
  23924. crispX - halfWidth, topBox,
  23925. 'L',
  23926. crispX + halfWidth, topBox,
  23927. 'L',
  23928. crispX + halfWidth, bottomBox,
  23929. 'Z', // Use a close statement to ensure a nice rectangle #2602
  23930. 'M',
  23931. crispX, topBox,
  23932. 'L',
  23933. crispX, hasTopWhisker ? Math.round(point.plotHigh) : topBox, // #460, #2094
  23934. 'M',
  23935. crispX, bottomBox,
  23936. 'L',
  23937. crispX, hasBottomWhisker ? Math.round(point.yBottom) : bottomBox // #460, #2094
  23938. );
  23939. graphic[isNew ? 'attr' : 'animate']({
  23940. d: path
  23941. })
  23942. .addClass(point.getClassName(), true);
  23943. }
  23944. });
  23945. }
  23946. });
  23947. /* ****************************************************************************
  23948. * End Candlestick series code *
  23949. *****************************************************************************/
  23950. }(Highcharts));
  23951. (function(H) {
  23952. /**
  23953. * (c) 2010-2017 Torstein Honsi
  23954. *
  23955. * License: www.highcharts.com/license
  23956. */
  23957. var addEvent = H.addEvent,
  23958. each = H.each,
  23959. merge = H.merge,
  23960. noop = H.noop,
  23961. Renderer = H.Renderer,
  23962. Series = H.Series,
  23963. seriesType = H.seriesType,
  23964. seriesTypes = H.seriesTypes,
  23965. SVGRenderer = H.SVGRenderer,
  23966. TrackerMixin = H.TrackerMixin,
  23967. VMLRenderer = H.VMLRenderer,
  23968. symbols = SVGRenderer.prototype.symbols,
  23969. stableSort = H.stableSort;
  23970. /**
  23971. * The flags series type.
  23972. *
  23973. * @constructor seriesTypes.flags
  23974. * @augments seriesTypes.column
  23975. */
  23976. seriesType('flags', 'column', {
  23977. pointRange: 0, // #673
  23978. //radius: 2,
  23979. shape: 'flag',
  23980. stackDistance: 12,
  23981. textAlign: 'center',
  23982. tooltip: {
  23983. pointFormat: '{point.text}<br/>'
  23984. },
  23985. threshold: null,
  23986. y: -30,
  23987. fillColor: '#ffffff',
  23988. // lineColor: color,
  23989. lineWidth: 1,
  23990. states: {
  23991. hover: {
  23992. lineColor: '#000000',
  23993. fillColor: '#ccd6eb'
  23994. }
  23995. },
  23996. style: {
  23997. fontSize: '11px',
  23998. fontWeight: 'bold'
  23999. }
  24000. }, /** @lends seriesTypes.flags.prototype */ {
  24001. sorted: false,
  24002. noSharedTooltip: true,
  24003. allowDG: false,
  24004. takeOrdinalPosition: false, // #1074
  24005. trackerGroups: ['markerGroup'],
  24006. forceCrop: true,
  24007. /**
  24008. * Inherit the initialization from base Series.
  24009. */
  24010. init: Series.prototype.init,
  24011. /**
  24012. * Get presentational attributes
  24013. */
  24014. pointAttribs: function(point, state) {
  24015. var options = this.options,
  24016. color = (point && point.color) || this.color,
  24017. lineColor = options.lineColor,
  24018. lineWidth = (point && point.lineWidth),
  24019. fill = (point && point.fillColor) || options.fillColor;
  24020. if (state) {
  24021. fill = options.states[state].fillColor;
  24022. lineColor = options.states[state].lineColor;
  24023. lineWidth = options.states[state].lineWidth;
  24024. }
  24025. return {
  24026. 'fill': fill || color,
  24027. 'stroke': lineColor || color,
  24028. 'stroke-width': lineWidth || options.lineWidth || 0
  24029. };
  24030. },
  24031. /**
  24032. * Extend the translate method by placing the point on the related series
  24033. */
  24034. translate: function() {
  24035. seriesTypes.column.prototype.translate.apply(this);
  24036. var series = this,
  24037. options = series.options,
  24038. chart = series.chart,
  24039. points = series.points,
  24040. cursor = points.length - 1,
  24041. point,
  24042. lastPoint,
  24043. optionsOnSeries = options.onSeries,
  24044. onSeries = optionsOnSeries && chart.get(optionsOnSeries),
  24045. onKey = options.onKey || 'y',
  24046. step = onSeries && onSeries.options.step,
  24047. onData = onSeries && onSeries.points,
  24048. i = onData && onData.length,
  24049. xAxis = series.xAxis,
  24050. yAxis = series.yAxis,
  24051. xAxisExt = xAxis.getExtremes(),
  24052. xOffset = 0,
  24053. leftPoint,
  24054. lastX,
  24055. rightPoint,
  24056. currentDataGrouping;
  24057. // relate to a master series
  24058. if (onSeries && onSeries.visible && i) {
  24059. xOffset = (onSeries.pointXOffset || 0) + (onSeries.barW || 0) / 2;
  24060. currentDataGrouping = onSeries.currentDataGrouping;
  24061. lastX = onData[i - 1].x + (currentDataGrouping ? currentDataGrouping.totalRange : 0); // #2374
  24062. // sort the data points
  24063. stableSort(points, function(a, b) {
  24064. return (a.x - b.x);
  24065. });
  24066. onKey = 'plot' + onKey[0].toUpperCase() + onKey.substr(1);
  24067. while (i-- && points[cursor]) {
  24068. point = points[cursor];
  24069. leftPoint = onData[i];
  24070. if (leftPoint.x <= point.x && leftPoint[onKey] !== undefined) {
  24071. if (point.x <= lastX) { // #803
  24072. point.plotY = leftPoint[onKey];
  24073. // interpolate between points, #666
  24074. if (leftPoint.x < point.x && !step) {
  24075. rightPoint = onData[i + 1];
  24076. if (rightPoint && rightPoint[onKey] !== undefined) {
  24077. point.plotY +=
  24078. ((point.x - leftPoint.x) / (rightPoint.x - leftPoint.x)) * // the distance ratio, between 0 and 1
  24079. (rightPoint[onKey] - leftPoint[onKey]); // the y distance
  24080. }
  24081. }
  24082. }
  24083. cursor--;
  24084. i++; // check again for points in the same x position
  24085. if (cursor < 0) {
  24086. break;
  24087. }
  24088. }
  24089. }
  24090. }
  24091. // Add plotY position and handle stacking
  24092. each(points, function(point, i) {
  24093. var stackIndex;
  24094. // Undefined plotY means the point is either on axis, outside series
  24095. // range or hidden series. If the series is outside the range of the
  24096. // x axis it should fall through with an undefined plotY, but then
  24097. // we must remove the shapeArgs (#847).
  24098. if (point.plotY === undefined) {
  24099. if (point.x >= xAxisExt.min && point.x <= xAxisExt.max) {
  24100. // we're inside xAxis range
  24101. point.plotY = chart.chartHeight - xAxis.bottom -
  24102. (xAxis.opposite ? xAxis.height : 0) +
  24103. xAxis.offset - yAxis.top; // #3517
  24104. } else {
  24105. point.shapeArgs = {}; // 847
  24106. }
  24107. }
  24108. point.plotX += xOffset; // #2049
  24109. // if multiple flags appear at the same x, order them into a stack
  24110. lastPoint = points[i - 1];
  24111. if (lastPoint && lastPoint.plotX === point.plotX) {
  24112. if (lastPoint.stackIndex === undefined) {
  24113. lastPoint.stackIndex = 0;
  24114. }
  24115. stackIndex = lastPoint.stackIndex + 1;
  24116. }
  24117. point.stackIndex = stackIndex; // #3639
  24118. });
  24119. },
  24120. /**
  24121. * Draw the markers
  24122. */
  24123. drawPoints: function() {
  24124. var series = this,
  24125. points = series.points,
  24126. chart = series.chart,
  24127. renderer = chart.renderer,
  24128. plotX,
  24129. plotY,
  24130. options = series.options,
  24131. optionsY = options.y,
  24132. shape,
  24133. i,
  24134. point,
  24135. graphic,
  24136. stackIndex,
  24137. anchorX,
  24138. anchorY,
  24139. outsideRight,
  24140. yAxis = series.yAxis;
  24141. i = points.length;
  24142. while (i--) {
  24143. point = points[i];
  24144. outsideRight = point.plotX > series.xAxis.len;
  24145. plotX = point.plotX;
  24146. stackIndex = point.stackIndex;
  24147. shape = point.options.shape || options.shape;
  24148. plotY = point.plotY;
  24149. if (plotY !== undefined) {
  24150. plotY = point.plotY + optionsY - (stackIndex !== undefined && stackIndex * options.stackDistance);
  24151. }
  24152. anchorX = stackIndex ? undefined : point.plotX; // skip connectors for higher level stacked points
  24153. anchorY = stackIndex ? undefined : point.plotY;
  24154. graphic = point.graphic;
  24155. // Only draw the point if y is defined and the flag is within the visible area
  24156. if (plotY !== undefined && plotX >= 0 && !outsideRight) {
  24157. // Create the flag
  24158. if (!graphic) {
  24159. graphic = point.graphic = renderer.label(
  24160. '',
  24161. null,
  24162. null,
  24163. shape,
  24164. null,
  24165. null,
  24166. options.useHTML
  24167. )
  24168. .attr(series.pointAttribs(point))
  24169. .css(merge(options.style, point.style))
  24170. .attr({
  24171. align: shape === 'flag' ? 'left' : 'center',
  24172. width: options.width,
  24173. height: options.height,
  24174. 'text-align': options.textAlign
  24175. })
  24176. .addClass('highcharts-point')
  24177. .add(series.markerGroup);
  24178. // Add reference to the point for tracker (#6303)
  24179. if (point.graphic.div) {
  24180. point.graphic.div.point = point;
  24181. }
  24182. graphic.shadow(options.shadow);
  24183. }
  24184. if (plotX > 0) { // #3119
  24185. plotX -= graphic.strokeWidth() % 2; // #4285
  24186. }
  24187. // Plant the flag
  24188. graphic.attr({
  24189. text: point.options.title || options.title || 'A',
  24190. x: plotX,
  24191. y: plotY,
  24192. anchorX: anchorX,
  24193. anchorY: anchorY
  24194. });
  24195. // Set the tooltip anchor position
  24196. point.tooltipPos = chart.inverted ? [yAxis.len + yAxis.pos - chart.plotLeft - plotY, series.xAxis.len - plotX] : [plotX, plotY + yAxis.pos - chart.plotTop]; // #6327
  24197. } else if (graphic) {
  24198. point.graphic = graphic.destroy();
  24199. }
  24200. }
  24201. // Might be a mix of SVG and HTML and we need events for both (#6303)
  24202. if (options.useHTML) {
  24203. H.wrap(series.markerGroup, 'on', function(proceed) {
  24204. return H.SVGElement.prototype.on.apply(
  24205. proceed.apply(this, [].slice.call(arguments, 1)), // for HTML
  24206. [].slice.call(arguments, 1)); // and for SVG
  24207. });
  24208. }
  24209. },
  24210. /**
  24211. * Extend the column trackers with listeners to expand and contract stacks
  24212. */
  24213. drawTracker: function() {
  24214. var series = this,
  24215. points = series.points;
  24216. TrackerMixin.drawTrackerPoint.apply(this);
  24217. // Bring each stacked flag up on mouse over, this allows readability of vertically
  24218. // stacked elements as well as tight points on the x axis. #1924.
  24219. each(points, function(point) {
  24220. var graphic = point.graphic;
  24221. if (graphic) {
  24222. addEvent(graphic.element, 'mouseover', function() {
  24223. // Raise this point
  24224. if (point.stackIndex > 0 && !point.raised) {
  24225. point._y = graphic.y;
  24226. graphic.attr({
  24227. y: point._y - 8
  24228. });
  24229. point.raised = true;
  24230. }
  24231. // Revert other raised points
  24232. each(points, function(otherPoint) {
  24233. if (otherPoint !== point && otherPoint.raised && otherPoint.graphic) {
  24234. otherPoint.graphic.attr({
  24235. y: otherPoint._y
  24236. });
  24237. otherPoint.raised = false;
  24238. }
  24239. });
  24240. });
  24241. }
  24242. });
  24243. },
  24244. animate: noop, // Disable animation
  24245. buildKDTree: noop,
  24246. setClip: noop
  24247. });
  24248. // create the flag icon with anchor
  24249. symbols.flag = function(x, y, w, h, options) {
  24250. var anchorX = (options && options.anchorX) || x,
  24251. anchorY = (options && options.anchorY) || y;
  24252. return [
  24253. 'M', anchorX, anchorY,
  24254. 'L', x, y + h,
  24255. x, y,
  24256. x + w, y,
  24257. x + w, y + h,
  24258. x, y + h,
  24259. 'Z'
  24260. ];
  24261. };
  24262. // create the circlepin and squarepin icons with anchor
  24263. each(['circle', 'square'], function(shape) {
  24264. symbols[shape + 'pin'] = function(x, y, w, h, options) {
  24265. var anchorX = options && options.anchorX,
  24266. anchorY = options && options.anchorY,
  24267. path,
  24268. labelTopOrBottomY;
  24269. // For single-letter flags, make sure circular flags are not taller than their width
  24270. if (shape === 'circle' && h > w) {
  24271. x -= Math.round((h - w) / 2);
  24272. w = h;
  24273. }
  24274. path = symbols[shape](x, y, w, h);
  24275. if (anchorX && anchorY) {
  24276. // if the label is below the anchor, draw the connecting line from the top edge of the label
  24277. // otherwise start drawing from the bottom edge
  24278. labelTopOrBottomY = (y > anchorY) ? y : y + h;
  24279. path.push('M', anchorX, labelTopOrBottomY, 'L', anchorX, anchorY);
  24280. }
  24281. return path;
  24282. };
  24283. });
  24284. // The symbol callbacks are generated on the SVGRenderer object in all browsers. Even
  24285. // VML browsers need this in order to generate shapes in export. Now share
  24286. // them with the VMLRenderer.
  24287. if (Renderer === VMLRenderer) {
  24288. each(['flag', 'circlepin', 'squarepin'], function(shape) {
  24289. VMLRenderer.prototype.symbols[shape] = symbols[shape];
  24290. });
  24291. }
  24292. /* ****************************************************************************
  24293. * End Flags series code *
  24294. *****************************************************************************/
  24295. }(Highcharts));
  24296. (function(H) {
  24297. /**
  24298. * (c) 2010-2017 Torstein Honsi
  24299. *
  24300. * License: www.highcharts.com/license
  24301. */
  24302. var addEvent = H.addEvent,
  24303. Axis = H.Axis,
  24304. correctFloat = H.correctFloat,
  24305. defaultOptions = H.defaultOptions,
  24306. defined = H.defined,
  24307. destroyObjectProperties = H.destroyObjectProperties,
  24308. doc = H.doc,
  24309. each = H.each,
  24310. fireEvent = H.fireEvent,
  24311. hasTouch = H.hasTouch,
  24312. isTouchDevice = H.isTouchDevice,
  24313. merge = H.merge,
  24314. pick = H.pick,
  24315. removeEvent = H.removeEvent,
  24316. svg = H.svg,
  24317. wrap = H.wrap,
  24318. swapXY;
  24319. var defaultScrollbarOptions = {
  24320. //enabled: true
  24321. height: isTouchDevice ? 20 : 14,
  24322. // trackBorderRadius: 0
  24323. barBorderRadius: 0,
  24324. buttonBorderRadius: 0,
  24325. liveRedraw: svg && !isTouchDevice,
  24326. margin: 10,
  24327. minWidth: 6,
  24328. //showFull: true,
  24329. //size: null,
  24330. step: 0.2,
  24331. zIndex: 3,
  24332. barBackgroundColor: '#cccccc',
  24333. barBorderWidth: 1,
  24334. barBorderColor: '#cccccc',
  24335. buttonArrowColor: '#333333',
  24336. buttonBackgroundColor: '#e6e6e6',
  24337. buttonBorderColor: '#cccccc',
  24338. buttonBorderWidth: 1,
  24339. rifleColor: '#333333',
  24340. trackBackgroundColor: '#f2f2f2',
  24341. trackBorderColor: '#f2f2f2',
  24342. trackBorderWidth: 1
  24343. };
  24344. defaultOptions.scrollbar = merge(true, defaultScrollbarOptions, defaultOptions.scrollbar);
  24345. /**
  24346. * When we have vertical scrollbar, rifles and arrow in buttons should be rotated.
  24347. * The same method is used in Navigator's handles, to rotate them.
  24348. * @param {Array} path - path to be rotated
  24349. * @param {Boolean} vertical - if vertical scrollbar, swap x-y values
  24350. */
  24351. H.swapXY = swapXY = function(path, vertical) {
  24352. var i,
  24353. len = path.length,
  24354. temp;
  24355. if (vertical) {
  24356. for (i = 0; i < len; i += 3) {
  24357. temp = path[i + 1];
  24358. path[i + 1] = path[i + 2];
  24359. path[i + 2] = temp;
  24360. }
  24361. }
  24362. return path;
  24363. };
  24364. /**
  24365. * A reusable scrollbar, internally used in Highstock's navigator and optionally
  24366. * on individual axes.
  24367. *
  24368. * @class
  24369. * @param {Object} renderer
  24370. * @param {Object} options
  24371. * @param {Object} chart
  24372. */
  24373. function Scrollbar(renderer, options, chart) { // docs
  24374. this.init(renderer, options, chart);
  24375. }
  24376. Scrollbar.prototype = {
  24377. init: function(renderer, options, chart) {
  24378. this.scrollbarButtons = [];
  24379. this.renderer = renderer;
  24380. this.userOptions = options;
  24381. this.options = merge(defaultScrollbarOptions, options);
  24382. this.chart = chart;
  24383. this.size = pick(this.options.size, this.options.height); // backward compatibility
  24384. // Init
  24385. if (options.enabled) {
  24386. this.render();
  24387. this.initEvents();
  24388. this.addEvents();
  24389. }
  24390. },
  24391. /**
  24392. * Render scrollbar with all required items.
  24393. */
  24394. render: function() {
  24395. var scroller = this,
  24396. renderer = scroller.renderer,
  24397. options = scroller.options,
  24398. size = scroller.size,
  24399. group;
  24400. // Draw the scrollbar group
  24401. scroller.group = group = renderer.g('scrollbar').attr({
  24402. zIndex: options.zIndex,
  24403. translateY: -99999
  24404. }).add();
  24405. // Draw the scrollbar track:
  24406. scroller.track = renderer.rect()
  24407. .addClass('highcharts-scrollbar-track')
  24408. .attr({
  24409. x: 0,
  24410. r: options.trackBorderRadius || 0,
  24411. height: size,
  24412. width: size
  24413. }).add(group);
  24414. scroller.track.attr({
  24415. fill: options.trackBackgroundColor,
  24416. stroke: options.trackBorderColor,
  24417. 'stroke-width': options.trackBorderWidth
  24418. });
  24419. this.trackBorderWidth = scroller.track.strokeWidth();
  24420. scroller.track.attr({
  24421. y: -this.trackBorderWidth % 2 / 2
  24422. });
  24423. // Draw the scrollbar itself
  24424. scroller.scrollbarGroup = renderer.g().add(group);
  24425. scroller.scrollbar = renderer.rect()
  24426. .addClass('highcharts-scrollbar-thumb')
  24427. .attr({
  24428. height: size,
  24429. width: size,
  24430. r: options.barBorderRadius || 0
  24431. }).add(scroller.scrollbarGroup);
  24432. scroller.scrollbarRifles = renderer.path(
  24433. swapXY([
  24434. 'M', -3, size / 4,
  24435. 'L', -3, 2 * size / 3,
  24436. 'M',
  24437. 0, size / 4,
  24438. 'L',
  24439. 0, 2 * size / 3,
  24440. 'M',
  24441. 3, size / 4,
  24442. 'L',
  24443. 3, 2 * size / 3
  24444. ], options.vertical))
  24445. .addClass('highcharts-scrollbar-rifles')
  24446. .add(scroller.scrollbarGroup);
  24447. scroller.scrollbar.attr({
  24448. fill: options.barBackgroundColor,
  24449. stroke: options.barBorderColor,
  24450. 'stroke-width': options.barBorderWidth
  24451. });
  24452. scroller.scrollbarRifles.attr({
  24453. stroke: options.rifleColor,
  24454. 'stroke-width': 1
  24455. });
  24456. scroller.scrollbarStrokeWidth = scroller.scrollbar.strokeWidth();
  24457. scroller.scrollbarGroup.translate(-scroller.scrollbarStrokeWidth % 2 / 2, -scroller.scrollbarStrokeWidth % 2 / 2);
  24458. // Draw the buttons:
  24459. scroller.drawScrollbarButton(0);
  24460. scroller.drawScrollbarButton(1);
  24461. },
  24462. /**
  24463. * Position the scrollbar, method called from a parent with defined dimensions
  24464. * @param {Number} x - x-position on the chart
  24465. * @param {Number} y - y-position on the chart
  24466. * @param {Number} width - width of the scrollbar
  24467. * @param {Number} height - height of the scorllbar
  24468. */
  24469. position: function(x, y, width, height) {
  24470. var scroller = this,
  24471. options = scroller.options,
  24472. vertical = options.vertical,
  24473. xOffset = height,
  24474. yOffset = 0,
  24475. method = scroller.rendered ? 'animate' : 'attr';
  24476. scroller.x = x;
  24477. scroller.y = y + this.trackBorderWidth;
  24478. scroller.width = width; // width with buttons
  24479. scroller.height = height;
  24480. scroller.xOffset = xOffset;
  24481. scroller.yOffset = yOffset;
  24482. // If Scrollbar is a vertical type, swap options:
  24483. if (vertical) {
  24484. scroller.width = scroller.yOffset = width = yOffset = scroller.size;
  24485. scroller.xOffset = xOffset = 0;
  24486. scroller.barWidth = height - width * 2; // width without buttons
  24487. scroller.x = x = x + scroller.options.margin;
  24488. } else {
  24489. scroller.height = scroller.xOffset = height = xOffset = scroller.size;
  24490. scroller.barWidth = width - height * 2; // width without buttons
  24491. scroller.y = scroller.y + scroller.options.margin;
  24492. }
  24493. // Set general position for a group:
  24494. scroller.group[method]({
  24495. translateX: x,
  24496. translateY: scroller.y
  24497. });
  24498. // Resize background/track:
  24499. scroller.track[method]({
  24500. width: width,
  24501. height: height
  24502. });
  24503. // Move right/bottom button ot it's place:
  24504. scroller.scrollbarButtons[1][method]({
  24505. translateX: vertical ? 0 : width - xOffset,
  24506. translateY: vertical ? height - yOffset : 0
  24507. });
  24508. },
  24509. /**
  24510. * Draw the scrollbar buttons with arrows
  24511. * @param {Number} index 0 is left, 1 is right
  24512. */
  24513. drawScrollbarButton: function(index) {
  24514. var scroller = this,
  24515. renderer = scroller.renderer,
  24516. scrollbarButtons = scroller.scrollbarButtons,
  24517. options = scroller.options,
  24518. size = scroller.size,
  24519. group,
  24520. tempElem;
  24521. group = renderer.g().add(scroller.group);
  24522. scrollbarButtons.push(group);
  24523. // Create a rectangle for the scrollbar button
  24524. tempElem = renderer.rect()
  24525. .addClass('highcharts-scrollbar-button')
  24526. .add(group);
  24527. // Presentational attributes
  24528. tempElem.attr({
  24529. stroke: options.buttonBorderColor,
  24530. 'stroke-width': options.buttonBorderWidth,
  24531. fill: options.buttonBackgroundColor
  24532. });
  24533. // Place the rectangle based on the rendered stroke width
  24534. tempElem.attr(tempElem.crisp({
  24535. x: -0.5,
  24536. y: -0.5,
  24537. width: size + 1, // +1 to compensate for crispifying in rect method
  24538. height: size + 1,
  24539. r: options.buttonBorderRadius
  24540. }, tempElem.strokeWidth()));
  24541. // Button arrow
  24542. tempElem = renderer
  24543. .path(swapXY([
  24544. 'M',
  24545. size / 2 + (index ? -1 : 1),
  24546. size / 2 - 3,
  24547. 'L',
  24548. size / 2 + (index ? -1 : 1),
  24549. size / 2 + 3,
  24550. 'L',
  24551. size / 2 + (index ? 2 : -2),
  24552. size / 2
  24553. ], options.vertical))
  24554. .addClass('highcharts-scrollbar-arrow')
  24555. .add(scrollbarButtons[index]);
  24556. tempElem.attr({
  24557. fill: options.buttonArrowColor
  24558. });
  24559. },
  24560. /**
  24561. * Set scrollbar size, with a given scale.
  24562. * @param {Number} from - scale (0-1) where bar should start
  24563. * @param {Number} to - scale (0-1) where bar should end
  24564. */
  24565. setRange: function(from, to) {
  24566. var scroller = this,
  24567. options = scroller.options,
  24568. vertical = options.vertical,
  24569. minWidth = options.minWidth,
  24570. fullWidth = scroller.barWidth,
  24571. fromPX,
  24572. toPX,
  24573. newPos,
  24574. newSize,
  24575. newRiflesPos,
  24576. method = this.rendered && !this.hasDragged ? 'animate' : 'attr';
  24577. if (!defined(fullWidth)) {
  24578. return;
  24579. }
  24580. from = Math.max(from, 0);
  24581. fromPX = Math.ceil(fullWidth * from);
  24582. toPX = fullWidth * Math.min(to, 1);
  24583. scroller.calculatedWidth = newSize = correctFloat(toPX - fromPX);
  24584. // We need to recalculate position, if minWidth is used
  24585. if (newSize < minWidth) {
  24586. fromPX = (fullWidth - minWidth + newSize) * from;
  24587. newSize = minWidth;
  24588. }
  24589. newPos = Math.floor(fromPX + scroller.xOffset + scroller.yOffset);
  24590. newRiflesPos = newSize / 2 - 0.5; // -0.5 -> rifle line width / 2
  24591. // Store current position:
  24592. scroller.from = from;
  24593. scroller.to = to;
  24594. if (!vertical) {
  24595. scroller.scrollbarGroup[method]({
  24596. translateX: newPos
  24597. });
  24598. scroller.scrollbar[method]({
  24599. width: newSize
  24600. });
  24601. scroller.scrollbarRifles[method]({
  24602. translateX: newRiflesPos
  24603. });
  24604. scroller.scrollbarLeft = newPos;
  24605. scroller.scrollbarTop = 0;
  24606. } else {
  24607. scroller.scrollbarGroup[method]({
  24608. translateY: newPos
  24609. });
  24610. scroller.scrollbar[method]({
  24611. height: newSize
  24612. });
  24613. scroller.scrollbarRifles[method]({
  24614. translateY: newRiflesPos
  24615. });
  24616. scroller.scrollbarTop = newPos;
  24617. scroller.scrollbarLeft = 0;
  24618. }
  24619. if (newSize <= 12) {
  24620. scroller.scrollbarRifles.hide();
  24621. } else {
  24622. scroller.scrollbarRifles.show(true);
  24623. }
  24624. // Show or hide the scrollbar based on the showFull setting
  24625. if (options.showFull === false) {
  24626. if (from <= 0 && to >= 1) {
  24627. scroller.group.hide();
  24628. } else {
  24629. scroller.group.show();
  24630. }
  24631. }
  24632. scroller.rendered = true;
  24633. },
  24634. /**
  24635. * Init events methods, so we have an access to the Scrollbar itself
  24636. */
  24637. initEvents: function() {
  24638. var scroller = this;
  24639. /**
  24640. * Event handler for the mouse move event.
  24641. */
  24642. scroller.mouseMoveHandler = function(e) {
  24643. var normalizedEvent = scroller.chart.pointer.normalize(e),
  24644. options = scroller.options,
  24645. direction = options.vertical ? 'chartY' : 'chartX',
  24646. initPositions = scroller.initPositions,
  24647. scrollPosition,
  24648. chartPosition,
  24649. change;
  24650. // In iOS, a mousemove event with e.pageX === 0 is fired when holding the finger
  24651. // down in the center of the scrollbar. This should be ignored.
  24652. if (scroller.grabbedCenter && (!e.touches || e.touches[0][direction] !== 0)) { // #4696, scrollbar failed on Android
  24653. chartPosition = scroller.cursorToScrollbarPosition(normalizedEvent)[direction];
  24654. scrollPosition = scroller[direction];
  24655. change = chartPosition - scrollPosition;
  24656. scroller.hasDragged = true;
  24657. scroller.updatePosition(initPositions[0] + change, initPositions[1] + change);
  24658. if (scroller.hasDragged) {
  24659. fireEvent(scroller, 'changed', {
  24660. from: scroller.from,
  24661. to: scroller.to,
  24662. trigger: 'scrollbar',
  24663. DOMType: e.type,
  24664. DOMEvent: e
  24665. });
  24666. }
  24667. }
  24668. };
  24669. /**
  24670. * Event handler for the mouse up event.
  24671. */
  24672. scroller.mouseUpHandler = function(e) {
  24673. if (scroller.hasDragged) {
  24674. fireEvent(scroller, 'changed', {
  24675. from: scroller.from,
  24676. to: scroller.to,
  24677. trigger: 'scrollbar',
  24678. DOMType: e.type,
  24679. DOMEvent: e
  24680. });
  24681. }
  24682. scroller.grabbedCenter = scroller.hasDragged = scroller.chartX = scroller.chartY = null;
  24683. };
  24684. scroller.mouseDownHandler = function(e) {
  24685. var normalizedEvent = scroller.chart.pointer.normalize(e),
  24686. mousePosition = scroller.cursorToScrollbarPosition(normalizedEvent);
  24687. scroller.chartX = mousePosition.chartX;
  24688. scroller.chartY = mousePosition.chartY;
  24689. scroller.initPositions = [scroller.from, scroller.to];
  24690. scroller.grabbedCenter = true;
  24691. };
  24692. scroller.buttonToMinClick = function(e) {
  24693. var range = correctFloat(scroller.to - scroller.from) * scroller.options.step;
  24694. scroller.updatePosition(correctFloat(scroller.from - range), correctFloat(scroller.to - range));
  24695. fireEvent(scroller, 'changed', {
  24696. from: scroller.from,
  24697. to: scroller.to,
  24698. trigger: 'scrollbar',
  24699. DOMEvent: e
  24700. });
  24701. };
  24702. scroller.buttonToMaxClick = function(e) {
  24703. var range = (scroller.to - scroller.from) * scroller.options.step;
  24704. scroller.updatePosition(scroller.from + range, scroller.to + range);
  24705. fireEvent(scroller, 'changed', {
  24706. from: scroller.from,
  24707. to: scroller.to,
  24708. trigger: 'scrollbar',
  24709. DOMEvent: e
  24710. });
  24711. };
  24712. scroller.trackClick = function(e) {
  24713. var normalizedEvent = scroller.chart.pointer.normalize(e),
  24714. range = scroller.to - scroller.from,
  24715. top = scroller.y + scroller.scrollbarTop,
  24716. left = scroller.x + scroller.scrollbarLeft;
  24717. if ((scroller.options.vertical && normalizedEvent.chartY > top) ||
  24718. (!scroller.options.vertical && normalizedEvent.chartX > left)) {
  24719. // On the top or on the left side of the track:
  24720. scroller.updatePosition(scroller.from + range, scroller.to + range);
  24721. } else {
  24722. // On the bottom or the right side of the track:
  24723. scroller.updatePosition(scroller.from - range, scroller.to - range);
  24724. }
  24725. fireEvent(scroller, 'changed', {
  24726. from: scroller.from,
  24727. to: scroller.to,
  24728. trigger: 'scrollbar',
  24729. DOMEvent: e
  24730. });
  24731. };
  24732. },
  24733. /**
  24734. * Get normalized (0-1) cursor position over the scrollbar
  24735. * @param {Event} normalizedEvent - normalized event, with chartX and chartY values
  24736. * @return {Object} Local position {chartX, chartY}
  24737. */
  24738. cursorToScrollbarPosition: function(normalizedEvent) {
  24739. var scroller = this,
  24740. options = scroller.options,
  24741. minWidthDifference = options.minWidth > scroller.calculatedWidth ? options.minWidth : 0; // minWidth distorts translation
  24742. return {
  24743. chartX: (normalizedEvent.chartX - scroller.x - scroller.xOffset) / (scroller.barWidth - minWidthDifference),
  24744. chartY: (normalizedEvent.chartY - scroller.y - scroller.yOffset) / (scroller.barWidth - minWidthDifference)
  24745. };
  24746. },
  24747. /**
  24748. * Update position option in the Scrollbar, with normalized 0-1 scale
  24749. */
  24750. updatePosition: function(from, to) {
  24751. if (to > 1) {
  24752. from = correctFloat(1 - correctFloat(to - from));
  24753. to = 1;
  24754. }
  24755. if (from < 0) {
  24756. to = correctFloat(to - from);
  24757. from = 0;
  24758. }
  24759. this.from = from;
  24760. this.to = to;
  24761. },
  24762. /**
  24763. * Update the scrollbar with new options
  24764. */
  24765. update: function(options) {
  24766. this.destroy();
  24767. this.init(this.chart.renderer, merge(true, this.options, options), this.chart);
  24768. },
  24769. /**
  24770. * Set up the mouse and touch events for the Scrollbar
  24771. */
  24772. addEvents: function() {
  24773. var buttonsOrder = this.options.inverted ? [1, 0] : [0, 1],
  24774. buttons = this.scrollbarButtons,
  24775. bar = this.scrollbarGroup.element,
  24776. track = this.track.element,
  24777. mouseDownHandler = this.mouseDownHandler,
  24778. mouseMoveHandler = this.mouseMoveHandler,
  24779. mouseUpHandler = this.mouseUpHandler,
  24780. _events;
  24781. // Mouse events
  24782. _events = [
  24783. [buttons[buttonsOrder[0]].element, 'click', this.buttonToMinClick],
  24784. [buttons[buttonsOrder[1]].element, 'click', this.buttonToMaxClick],
  24785. [track, 'click', this.trackClick],
  24786. [bar, 'mousedown', mouseDownHandler],
  24787. [doc, 'mousemove', mouseMoveHandler],
  24788. [doc, 'mouseup', mouseUpHandler]
  24789. ];
  24790. // Touch events
  24791. if (hasTouch) {
  24792. _events.push(
  24793. [bar, 'touchstart', mouseDownHandler], [doc, 'touchmove', mouseMoveHandler], [doc, 'touchend', mouseUpHandler]
  24794. );
  24795. }
  24796. // Add them all
  24797. each(_events, function(args) {
  24798. addEvent.apply(null, args);
  24799. });
  24800. this._events = _events;
  24801. },
  24802. /**
  24803. * Removes the event handlers attached previously with addEvents.
  24804. */
  24805. removeEvents: function() {
  24806. each(this._events, function(args) {
  24807. removeEvent.apply(null, args);
  24808. });
  24809. this._events.length = 0;
  24810. },
  24811. /**
  24812. * Destroys allocated elements.
  24813. */
  24814. destroy: function() {
  24815. var scroller = this.chart.scroller;
  24816. // Disconnect events added in addEvents
  24817. this.removeEvents();
  24818. // Destroy properties
  24819. each(['track', 'scrollbarRifles', 'scrollbar', 'scrollbarGroup', 'group'], function(prop) {
  24820. if (this[prop] && this[prop].destroy) {
  24821. this[prop] = this[prop].destroy();
  24822. }
  24823. }, this);
  24824. if (scroller && this === scroller.scrollbar) { // #6421, chart may have more scrollbars
  24825. scroller.scrollbar = null;
  24826. // Destroy elements in collection
  24827. destroyObjectProperties(scroller.scrollbarButtons);
  24828. }
  24829. }
  24830. };
  24831. /**
  24832. * Wrap axis initialization and create scrollbar if enabled:
  24833. */
  24834. wrap(Axis.prototype, 'init', function(proceed) {
  24835. var axis = this;
  24836. proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
  24837. if (axis.options.scrollbar && axis.options.scrollbar.enabled) {
  24838. // Predefined options:
  24839. axis.options.scrollbar.vertical = !axis.horiz;
  24840. axis.options.startOnTick = axis.options.endOnTick = false;
  24841. axis.scrollbar = new Scrollbar(axis.chart.renderer, axis.options.scrollbar, axis.chart);
  24842. addEvent(axis.scrollbar, 'changed', function(e) {
  24843. var unitedMin = Math.min(pick(axis.options.min, axis.min), axis.min, axis.dataMin),
  24844. unitedMax = Math.max(pick(axis.options.max, axis.max), axis.max, axis.dataMax),
  24845. range = unitedMax - unitedMin,
  24846. to,
  24847. from;
  24848. if ((axis.horiz && !axis.reversed) || (!axis.horiz && axis.reversed)) {
  24849. to = unitedMin + range * this.to;
  24850. from = unitedMin + range * this.from;
  24851. } else {
  24852. // y-values in browser are reversed, but this also applies for reversed horizontal axis:
  24853. to = unitedMin + range * (1 - this.from);
  24854. from = unitedMin + range * (1 - this.to);
  24855. }
  24856. axis.setExtremes(from, to, true, false, e);
  24857. });
  24858. }
  24859. });
  24860. /**
  24861. * Wrap rendering axis, and update scrollbar if one is created:
  24862. */
  24863. wrap(Axis.prototype, 'render', function(proceed) {
  24864. var axis = this,
  24865. scrollMin = Math.min(pick(axis.options.min, axis.min), axis.min, axis.dataMin),
  24866. scrollMax = Math.max(pick(axis.options.max, axis.max), axis.max, axis.dataMax),
  24867. scrollbar = axis.scrollbar,
  24868. titleOffset = axis.titleOffset || 0,
  24869. offsetsIndex,
  24870. from,
  24871. to;
  24872. proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
  24873. if (scrollbar) {
  24874. if (axis.horiz) {
  24875. scrollbar.position(
  24876. axis.left,
  24877. axis.top + axis.height + 2 + axis.chart.scrollbarsOffsets[1] +
  24878. (axis.opposite ?
  24879. 0 :
  24880. titleOffset + axis.axisTitleMargin + axis.offset
  24881. ),
  24882. axis.width,
  24883. axis.height
  24884. );
  24885. offsetsIndex = 1;
  24886. } else {
  24887. scrollbar.position(
  24888. axis.left + axis.width + 2 + axis.chart.scrollbarsOffsets[0] +
  24889. (axis.opposite ?
  24890. titleOffset + axis.axisTitleMargin + axis.offset :
  24891. 0
  24892. ),
  24893. axis.top,
  24894. axis.width,
  24895. axis.height
  24896. );
  24897. offsetsIndex = 0;
  24898. }
  24899. if ((!axis.opposite && !axis.horiz) || (axis.opposite && axis.horiz)) {
  24900. axis.chart.scrollbarsOffsets[offsetsIndex] +=
  24901. axis.scrollbar.size + axis.scrollbar.options.margin;
  24902. }
  24903. if (isNaN(scrollMin) || isNaN(scrollMax) || !defined(axis.min) || !defined(axis.max)) {
  24904. scrollbar.setRange(0, 0); // default action: when there is not extremes on the axis, but scrollbar exists, make it full size
  24905. } else {
  24906. from = (axis.min - scrollMin) / (scrollMax - scrollMin);
  24907. to = (axis.max - scrollMin) / (scrollMax - scrollMin);
  24908. if ((axis.horiz && !axis.reversed) || (!axis.horiz && axis.reversed)) {
  24909. scrollbar.setRange(from, to);
  24910. } else {
  24911. scrollbar.setRange(1 - to, 1 - from); // inverse vertical axis
  24912. }
  24913. }
  24914. }
  24915. });
  24916. /**
  24917. * Make space for a scrollbar
  24918. */
  24919. wrap(Axis.prototype, 'getOffset', function(proceed) {
  24920. var axis = this,
  24921. index = axis.horiz ? 2 : 1,
  24922. scrollbar = axis.scrollbar;
  24923. proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
  24924. if (scrollbar) {
  24925. axis.chart.scrollbarsOffsets = [0, 0]; // reset scrollbars offsets
  24926. axis.chart.axisOffset[index] += scrollbar.size + scrollbar.options.margin;
  24927. }
  24928. });
  24929. /**
  24930. * Destroy scrollbar when connected to the specific axis
  24931. */
  24932. wrap(Axis.prototype, 'destroy', function(proceed) {
  24933. if (this.scrollbar) {
  24934. this.scrollbar = this.scrollbar.destroy();
  24935. }
  24936. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  24937. });
  24938. H.Scrollbar = Scrollbar;
  24939. }(Highcharts));
  24940. (function(H) {
  24941. /**
  24942. * (c) 2010-2017 Torstein Honsi
  24943. *
  24944. * License: www.highcharts.com/license
  24945. */
  24946. /* ****************************************************************************
  24947. * Start Navigator code *
  24948. *****************************************************************************/
  24949. var addEvent = H.addEvent,
  24950. Axis = H.Axis,
  24951. Chart = H.Chart,
  24952. color = H.color,
  24953. defaultDataGroupingUnits = H.defaultDataGroupingUnits,
  24954. defaultOptions = H.defaultOptions,
  24955. defined = H.defined,
  24956. destroyObjectProperties = H.destroyObjectProperties,
  24957. doc = H.doc,
  24958. each = H.each,
  24959. erase = H.erase,
  24960. error = H.error,
  24961. extend = H.extend,
  24962. grep = H.grep,
  24963. hasTouch = H.hasTouch,
  24964. isNumber = H.isNumber,
  24965. isObject = H.isObject,
  24966. merge = H.merge,
  24967. pick = H.pick,
  24968. removeEvent = H.removeEvent,
  24969. Scrollbar = H.Scrollbar,
  24970. Series = H.Series,
  24971. seriesTypes = H.seriesTypes,
  24972. wrap = H.wrap,
  24973. swapXY = H.swapXY,
  24974. units = [].concat(defaultDataGroupingUnits), // copy
  24975. defaultSeriesType,
  24976. // Finding the min or max of a set of variables where we don't know if they are defined,
  24977. // is a pattern that is repeated several places in Highcharts. Consider making this
  24978. // a global utility method.
  24979. numExt = function(extreme) {
  24980. var numbers = grep(arguments, isNumber);
  24981. if (numbers.length) {
  24982. return Math[extreme].apply(0, numbers);
  24983. }
  24984. };
  24985. // add more resolution to units
  24986. units[4] = ['day', [1, 2, 3, 4]]; // allow more days
  24987. units[5] = ['week', [1, 2, 3]]; // allow more weeks
  24988. defaultSeriesType = seriesTypes.areaspline === undefined ? 'line' : 'areaspline';
  24989. extend(defaultOptions, {
  24990. navigator: {
  24991. //enabled: true,
  24992. height: 40,
  24993. margin: 25,
  24994. maskInside: true,
  24995. handles: {
  24996. backgroundColor: '#f2f2f2',
  24997. borderColor: '#999999'
  24998. },
  24999. maskFill: color('#6685c2').setOpacity(0.3).get(),
  25000. outlineColor: '#cccccc',
  25001. outlineWidth: 1,
  25002. series: {
  25003. type: defaultSeriesType,
  25004. color: '#335cad',
  25005. fillOpacity: 0.05,
  25006. lineWidth: 1,
  25007. compare: null,
  25008. dataGrouping: {
  25009. approximation: 'average',
  25010. enabled: true,
  25011. groupPixelWidth: 2,
  25012. smoothed: true,
  25013. units: units
  25014. },
  25015. dataLabels: {
  25016. enabled: false,
  25017. zIndex: 2 // #1839
  25018. },
  25019. id: 'highcharts-navigator-series',
  25020. className: 'highcharts-navigator-series',
  25021. lineColor: null, // Allow color setting while disallowing default candlestick setting (#4602)
  25022. marker: {
  25023. enabled: false
  25024. },
  25025. pointRange: 0,
  25026. shadow: false,
  25027. threshold: null
  25028. },
  25029. //top: undefined,
  25030. //opposite: undefined,
  25031. xAxis: {
  25032. className: 'highcharts-navigator-xaxis',
  25033. tickLength: 0,
  25034. lineWidth: 0,
  25035. gridLineColor: '#e6e6e6',
  25036. gridLineWidth: 1,
  25037. tickPixelInterval: 200,
  25038. labels: {
  25039. align: 'left',
  25040. style: {
  25041. color: '#999999'
  25042. },
  25043. x: 3,
  25044. y: -4
  25045. },
  25046. crosshair: false
  25047. },
  25048. yAxis: {
  25049. className: 'highcharts-navigator-yaxis',
  25050. gridLineWidth: 0,
  25051. startOnTick: false,
  25052. endOnTick: false,
  25053. minPadding: 0.1,
  25054. maxPadding: 0.1,
  25055. labels: {
  25056. enabled: false
  25057. },
  25058. crosshair: false,
  25059. title: {
  25060. text: null
  25061. },
  25062. tickLength: 0,
  25063. tickWidth: 0
  25064. }
  25065. }
  25066. });
  25067. /**
  25068. * The Navigator class
  25069. * @param {Object} chart - Chart object
  25070. * @class
  25071. */
  25072. function Navigator(chart) {
  25073. this.init(chart);
  25074. }
  25075. Navigator.prototype = {
  25076. /**
  25077. * Draw one of the handles on the side of the zoomed range in the navigator
  25078. * @param {Number} x The x center for the handle
  25079. * @param {Number} index 0 for left and 1 for right
  25080. * @param {Boolean} inverted flag for chart.inverted
  25081. * @param {String} verb use 'animate' or 'attr'
  25082. */
  25083. drawHandle: function(x, index, inverted, verb) {
  25084. var navigator = this;
  25085. // Place it
  25086. navigator.handles[index][verb](inverted ? {
  25087. translateX: Math.round(navigator.left + navigator.height / 2 - 8),
  25088. translateY: Math.round(navigator.top + parseInt(x, 10) + 0.5)
  25089. } : {
  25090. translateX: Math.round(navigator.left + parseInt(x, 10)),
  25091. translateY: Math.round(navigator.top + navigator.height / 2 - 8)
  25092. });
  25093. },
  25094. /**
  25095. * Draw one of the handles on the side of the zoomed range in the navigator
  25096. * @param {Boolean} inverted flag for chart.inverted
  25097. * @returns {Array} Path to be used in a handle
  25098. */
  25099. getHandlePath: function(inverted) {
  25100. return swapXY([
  25101. 'M', -4.5, 0.5,
  25102. 'L',
  25103. 3.5, 0.5,
  25104. 'L',
  25105. 3.5, 15.5,
  25106. 'L', -4.5, 15.5,
  25107. 'L', -4.5, 0.5,
  25108. 'M', -1.5, 4,
  25109. 'L', -1.5, 12,
  25110. 'M',
  25111. 0.5, 4,
  25112. 'L',
  25113. 0.5, 12
  25114. ], inverted);
  25115. },
  25116. /**
  25117. * Render outline around the zoomed range
  25118. * @param {Number} zoomedMin in pixels position where zoomed range starts
  25119. * @param {Number} zoomedMax in pixels position where zoomed range ends
  25120. * @param {Boolean} inverted flag if chart is inverted
  25121. * @param {String} verb use 'animate' or 'attr'
  25122. */
  25123. drawOutline: function(zoomedMin, zoomedMax, inverted, verb) {
  25124. var navigator = this,
  25125. maskInside = navigator.navigatorOptions.maskInside,
  25126. outlineWidth = navigator.outline.strokeWidth(),
  25127. halfOutline = outlineWidth / 2,
  25128. outlineCorrection = (outlineWidth % 2) / 2, // #5800
  25129. outlineHeight = navigator.outlineHeight,
  25130. scrollbarHeight = navigator.scrollbarHeight,
  25131. navigatorSize = navigator.size,
  25132. left = navigator.left - scrollbarHeight,
  25133. navigatorTop = navigator.top,
  25134. verticalMin,
  25135. path;
  25136. if (inverted) {
  25137. left -= halfOutline;
  25138. verticalMin = navigatorTop + zoomedMax + outlineCorrection;
  25139. zoomedMax = navigatorTop + zoomedMin + outlineCorrection;
  25140. path = [
  25141. 'M',
  25142. left + outlineHeight,
  25143. navigatorTop - scrollbarHeight - outlineCorrection, // top edge
  25144. 'L',
  25145. left + outlineHeight,
  25146. verticalMin, // top right of zoomed range
  25147. 'L',
  25148. left,
  25149. verticalMin, // top left of z.r.
  25150. 'L',
  25151. left,
  25152. zoomedMax, // bottom left of z.r.
  25153. 'L',
  25154. left + outlineHeight,
  25155. zoomedMax, // bottom right of z.r.
  25156. 'L',
  25157. left + outlineHeight,
  25158. navigatorTop + navigatorSize + scrollbarHeight // bottom edge
  25159. ].concat(maskInside ? [
  25160. 'M',
  25161. left + outlineHeight,
  25162. verticalMin - halfOutline, // upper left of zoomed range
  25163. 'L',
  25164. left + outlineHeight,
  25165. zoomedMax + halfOutline // upper right of z.r.
  25166. ] : []);
  25167. } else {
  25168. zoomedMin += left + scrollbarHeight - outlineCorrection;
  25169. zoomedMax += left + scrollbarHeight - outlineCorrection;
  25170. navigatorTop += halfOutline;
  25171. path = [
  25172. 'M',
  25173. left,
  25174. navigatorTop, // left
  25175. 'L',
  25176. zoomedMin,
  25177. navigatorTop, // upper left of zoomed range
  25178. 'L',
  25179. zoomedMin,
  25180. navigatorTop + outlineHeight, // lower left of z.r.
  25181. 'L',
  25182. zoomedMax,
  25183. navigatorTop + outlineHeight, // lower right of z.r.
  25184. 'L',
  25185. zoomedMax,
  25186. navigatorTop, // upper right of z.r.
  25187. 'L',
  25188. left + navigatorSize + scrollbarHeight * 2,
  25189. navigatorTop // right
  25190. ].concat(maskInside ? [
  25191. 'M',
  25192. zoomedMin - halfOutline,
  25193. navigatorTop, // upper left of zoomed range
  25194. 'L',
  25195. zoomedMax + halfOutline,
  25196. navigatorTop // upper right of z.r.
  25197. ] : []);
  25198. }
  25199. navigator.outline[verb]({
  25200. d: path
  25201. });
  25202. },
  25203. /**
  25204. * Render outline around the zoomed range
  25205. * @param {Number} zoomedMin in pixels position where zoomed range starts
  25206. * @param {Number} zoomedMax in pixels position where zoomed range ends
  25207. * @param {Boolean} inverted flag if chart is inverted
  25208. * @param {String} verb use 'animate' or 'attr'
  25209. */
  25210. drawMasks: function(zoomedMin, zoomedMax, inverted, verb) {
  25211. var navigator = this,
  25212. left = navigator.left,
  25213. top = navigator.top,
  25214. navigatorHeight = navigator.height,
  25215. height,
  25216. width,
  25217. x,
  25218. y;
  25219. // Determine rectangle position & size
  25220. // According to (non)inverted position:
  25221. if (inverted) {
  25222. x = [left, left, left];
  25223. y = [top, top + zoomedMin, top + zoomedMax];
  25224. width = [navigatorHeight, navigatorHeight, navigatorHeight];
  25225. height = [
  25226. zoomedMin,
  25227. zoomedMax - zoomedMin,
  25228. navigator.size - zoomedMax
  25229. ];
  25230. } else {
  25231. x = [left, left + zoomedMin, left + zoomedMax];
  25232. y = [top, top, top];
  25233. width = [
  25234. zoomedMin,
  25235. zoomedMax - zoomedMin,
  25236. navigator.size - zoomedMax
  25237. ];
  25238. height = [navigatorHeight, navigatorHeight, navigatorHeight];
  25239. }
  25240. each(navigator.shades, function(shade, i) {
  25241. shade[verb]({
  25242. x: x[i],
  25243. y: y[i],
  25244. width: width[i],
  25245. height: height[i]
  25246. });
  25247. });
  25248. },
  25249. /**
  25250. * Generate DOM elements for a navigator:
  25251. * - main navigator group
  25252. * - all shades
  25253. * - outline
  25254. * - handles
  25255. */
  25256. renderElements: function() {
  25257. var navigator = this,
  25258. navigatorOptions = navigator.navigatorOptions,
  25259. maskInside = navigatorOptions.maskInside,
  25260. chart = navigator.chart,
  25261. inverted = chart.inverted,
  25262. renderer = chart.renderer,
  25263. navigatorGroup;
  25264. // Create the main navigator group
  25265. navigator.navigatorGroup = navigatorGroup = renderer.g('navigator')
  25266. .attr({
  25267. zIndex: 8,
  25268. visibility: 'hidden'
  25269. })
  25270. .add();
  25271. var mouseCursor = {
  25272. cursor: inverted ? 'ns-resize' : 'ew-resize'
  25273. };
  25274. // Create masks, each mask will get events and fill:
  25275. each([!maskInside, maskInside, !maskInside], function(hasMask, index) {
  25276. navigator.shades[index] = renderer.rect()
  25277. .addClass('highcharts-navigator-mask' +
  25278. (index === 1 ? '-inside' : '-outside'))
  25279. .attr({
  25280. fill: hasMask ? navigatorOptions.maskFill : 'rgba(0,0,0,0)'
  25281. })
  25282. .css(index === 1 && mouseCursor)
  25283. .add(navigatorGroup);
  25284. });
  25285. // Create the outline:
  25286. navigator.outline = renderer.path()
  25287. .addClass('highcharts-navigator-outline')
  25288. .attr({
  25289. 'stroke-width': navigatorOptions.outlineWidth,
  25290. stroke: navigatorOptions.outlineColor
  25291. })
  25292. .add(navigatorGroup);
  25293. // Create the handlers:
  25294. each([0, 1], function(index) {
  25295. navigator.handles[index] = renderer
  25296. .path(navigator.getHandlePath(inverted))
  25297. // zIndex = 6 for right handle, 7 for left.
  25298. // Can't be 10, because of the tooltip in inverted chart #2908
  25299. .attr({
  25300. zIndex: 7 - index
  25301. })
  25302. .addClass(
  25303. 'highcharts-navigator-handle highcharts-navigator-handle-' + ['left', 'right'][index]
  25304. ).add(navigatorGroup);
  25305. var handlesOptions = navigatorOptions.handles;
  25306. navigator.handles[index]
  25307. .attr({
  25308. fill: handlesOptions.backgroundColor,
  25309. stroke: handlesOptions.borderColor,
  25310. 'stroke-width': 1
  25311. })
  25312. .css(mouseCursor);
  25313. });
  25314. },
  25315. /**
  25316. * Update navigator
  25317. * @param {Object} options Options to merge in when updating navigator
  25318. */
  25319. update: function(options) {
  25320. this.destroy();
  25321. var chartOptions = this.chart.options;
  25322. merge(true, chartOptions.navigator, this.options, options);
  25323. this.init(this.chart);
  25324. },
  25325. /**
  25326. * Render the navigator
  25327. * @param {Number} min X axis value minimum
  25328. * @param {Number} max X axis value maximum
  25329. * @param {Number} pxMin Pixel value minimum
  25330. * @param {Number} pxMax Pixel value maximum
  25331. */
  25332. render: function(min, max, pxMin, pxMax) {
  25333. var navigator = this,
  25334. chart = navigator.chart,
  25335. navigatorWidth,
  25336. scrollbarLeft,
  25337. scrollbarTop,
  25338. scrollbarHeight = navigator.scrollbarHeight,
  25339. navigatorSize,
  25340. xAxis = navigator.xAxis,
  25341. scrollbarXAxis = xAxis.fake ? chart.xAxis[0] : xAxis,
  25342. navigatorEnabled = navigator.navigatorEnabled,
  25343. zoomedMin,
  25344. zoomedMax,
  25345. rendered = navigator.rendered,
  25346. inverted = chart.inverted,
  25347. verb,
  25348. newMin,
  25349. newMax,
  25350. minRange = chart.xAxis[0].minRange;
  25351. // Don't redraw while moving the handles (#4703).
  25352. if (this.hasDragged && !defined(pxMin)) {
  25353. return;
  25354. }
  25355. // Don't render the navigator until we have data (#486, #4202, #5172).
  25356. if (!isNumber(min) || !isNumber(max)) {
  25357. // However, if navigator was already rendered, we may need to resize
  25358. // it. For example hidden series, but visible navigator (#6022).
  25359. if (rendered) {
  25360. pxMin = 0;
  25361. pxMax = xAxis.width;
  25362. } else {
  25363. return;
  25364. }
  25365. }
  25366. navigator.left = pick(
  25367. xAxis.left,
  25368. // in case of scrollbar only, without navigator
  25369. chart.plotLeft + scrollbarHeight + (inverted ? chart.plotWidth : 0)
  25370. );
  25371. navigator.size = zoomedMax = navigatorSize = pick(
  25372. xAxis.len,
  25373. (inverted ? chart.plotHeight : chart.plotWidth) - 2 * scrollbarHeight
  25374. );
  25375. if (inverted) {
  25376. navigatorWidth = scrollbarHeight;
  25377. } else {
  25378. navigatorWidth = navigatorSize + 2 * scrollbarHeight;
  25379. }
  25380. // Get the pixel position of the handles
  25381. pxMin = pick(pxMin, xAxis.toPixels(min, true));
  25382. pxMax = pick(pxMax, xAxis.toPixels(max, true));
  25383. if (!isNumber(pxMin) || Math.abs(pxMin) === Infinity) { // Verify (#1851, #2238)
  25384. pxMin = 0;
  25385. pxMax = navigatorWidth;
  25386. }
  25387. // Are we below the minRange? (#2618, #6191)
  25388. newMin = xAxis.toValue(pxMin, true);
  25389. newMax = xAxis.toValue(pxMax, true);
  25390. if (Math.abs(newMax - newMin) < minRange) {
  25391. if (this.grabbedLeft) {
  25392. pxMin = xAxis.toPixels(newMax - minRange, true);
  25393. } else if (this.grabbedRight) {
  25394. pxMax = xAxis.toPixels(newMin + minRange, true);
  25395. } else {
  25396. return;
  25397. }
  25398. }
  25399. // Handles are allowed to cross, but never exceed the plot area
  25400. navigator.zoomedMax = Math.min(Math.max(pxMin, pxMax, 0), zoomedMax);
  25401. navigator.zoomedMin = Math.min(
  25402. Math.max(
  25403. navigator.fixedWidth ?
  25404. navigator.zoomedMax - navigator.fixedWidth :
  25405. Math.min(pxMin, pxMax),
  25406. 0
  25407. ),
  25408. zoomedMax
  25409. );
  25410. navigator.range = navigator.zoomedMax - navigator.zoomedMin;
  25411. zoomedMax = Math.round(navigator.zoomedMax);
  25412. zoomedMin = Math.round(navigator.zoomedMin);
  25413. if (navigatorEnabled) {
  25414. navigator.navigatorGroup.attr({
  25415. visibility: 'visible'
  25416. });
  25417. // Place elements
  25418. verb = rendered && !navigator.hasDragged ? 'animate' : 'attr';
  25419. navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
  25420. navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
  25421. navigator.drawHandle(zoomedMin, 0, inverted, verb);
  25422. navigator.drawHandle(zoomedMax, 1, inverted, verb);
  25423. }
  25424. if (navigator.scrollbar) {
  25425. if (inverted) {
  25426. scrollbarTop = navigator.top - scrollbarHeight;
  25427. scrollbarLeft = navigator.left - scrollbarHeight +
  25428. (navigatorEnabled || !scrollbarXAxis.opposite ? 0 :
  25429. // Multiple axes has offsets:
  25430. (scrollbarXAxis.titleOffset || 0) +
  25431. // Self margin from the axis.title
  25432. scrollbarXAxis.axisTitleMargin
  25433. );
  25434. scrollbarHeight = navigatorSize + 2 * scrollbarHeight;
  25435. } else {
  25436. scrollbarTop = navigator.top +
  25437. (navigatorEnabled ? navigator.height : -scrollbarHeight);
  25438. scrollbarLeft = navigator.left - scrollbarHeight;
  25439. }
  25440. // Reposition scrollbar
  25441. navigator.scrollbar.position(
  25442. scrollbarLeft,
  25443. scrollbarTop,
  25444. navigatorWidth,
  25445. scrollbarHeight
  25446. );
  25447. // Keep scale 0-1
  25448. navigator.scrollbar.setRange(
  25449. // Use real value, not rounded because range can be very small (#1716)
  25450. navigator.zoomedMin / navigatorSize,
  25451. navigator.zoomedMax / navigatorSize
  25452. );
  25453. }
  25454. navigator.rendered = true;
  25455. },
  25456. /**
  25457. * Set up the mouse and touch events for the navigator
  25458. */
  25459. addMouseEvents: function() {
  25460. var navigator = this,
  25461. chart = navigator.chart,
  25462. container = chart.container,
  25463. eventsToUnbind = [],
  25464. mouseMoveHandler,
  25465. mouseUpHandler;
  25466. /**
  25467. * Create mouse events' handlers.
  25468. * Make them as separate functions to enable wrapping them:
  25469. */
  25470. navigator.mouseMoveHandler = mouseMoveHandler = function(e) {
  25471. navigator.onMouseMove(e);
  25472. };
  25473. navigator.mouseUpHandler = mouseUpHandler = function(e) {
  25474. navigator.onMouseUp(e);
  25475. };
  25476. // Add shades and handles mousedown events
  25477. eventsToUnbind = navigator.getPartsEvents('mousedown');
  25478. // Add mouse move and mouseup events. These are bind to doc/container,
  25479. // because Navigator.grabbedSomething flags are stored in mousedown events:
  25480. eventsToUnbind.push(
  25481. addEvent(container, 'mousemove', mouseMoveHandler),
  25482. addEvent(doc, 'mouseup', mouseUpHandler)
  25483. );
  25484. // Touch events
  25485. if (hasTouch) {
  25486. eventsToUnbind.push(
  25487. addEvent(container, 'touchmove', mouseMoveHandler),
  25488. addEvent(doc, 'touchend', mouseUpHandler)
  25489. );
  25490. eventsToUnbind.concat(navigator.getPartsEvents('touchstart'));
  25491. }
  25492. navigator.eventsToUnbind = eventsToUnbind;
  25493. // Data events
  25494. if (navigator.series && navigator.series[0]) {
  25495. eventsToUnbind.push(
  25496. addEvent(navigator.series[0].xAxis, 'foundExtremes', function() {
  25497. chart.navigator.modifyNavigatorAxisExtremes();
  25498. })
  25499. );
  25500. }
  25501. },
  25502. /**
  25503. * Generate events for handles and masks
  25504. * @param {String} eventName Event name handler, 'mousedown' or 'touchstart'
  25505. * @returns {Array} An array of arrays: [DOMElement, eventName, callback].
  25506. */
  25507. getPartsEvents: function(eventName) {
  25508. var navigator = this,
  25509. events = [];
  25510. each(['shades', 'handles'], function(name) {
  25511. each(navigator[name], function(navigatorItem, index) {
  25512. events.push(
  25513. addEvent(
  25514. navigatorItem.element,
  25515. eventName,
  25516. function(e) {
  25517. navigator[name + 'Mousedown'](e, index);
  25518. }
  25519. )
  25520. );
  25521. });
  25522. });
  25523. return events;
  25524. },
  25525. /**
  25526. * Mousedown on a shaded mask, either:
  25527. * - will be stored for future drag&drop
  25528. * - will directly shift to a new range
  25529. *
  25530. * @param {Object} e Mouse event
  25531. * @param {Number} index Index of a mask in Navigator.shades array
  25532. */
  25533. shadesMousedown: function(e, index) {
  25534. e = this.chart.pointer.normalize(e);
  25535. var navigator = this,
  25536. chart = navigator.chart,
  25537. xAxis = navigator.xAxis,
  25538. zoomedMin = navigator.zoomedMin,
  25539. navigatorPosition = navigator.left,
  25540. navigatorSize = navigator.size,
  25541. range = navigator.range,
  25542. chartX = e.chartX,
  25543. fixedMax,
  25544. ext,
  25545. left;
  25546. // For inverted chart, swap some options:
  25547. if (chart.inverted) {
  25548. chartX = e.chartY;
  25549. navigatorPosition = navigator.top;
  25550. }
  25551. if (index === 1) {
  25552. // Store information for drag&drop
  25553. navigator.grabbedCenter = chartX;
  25554. navigator.fixedWidth = range;
  25555. navigator.dragOffset = chartX - zoomedMin;
  25556. } else {
  25557. // Shift the range by clicking on shaded areas
  25558. left = chartX - navigatorPosition - range / 2;
  25559. if (index === 0) {
  25560. left = Math.max(0, left);
  25561. } else if (index === 2 && left + range >= navigatorSize) {
  25562. left = navigatorSize - range;
  25563. fixedMax = navigator.getUnionExtremes().dataMax; // #2293, #3543
  25564. }
  25565. if (left !== zoomedMin) { // it has actually moved
  25566. navigator.fixedWidth = range; // #1370
  25567. ext = xAxis.toFixedRange(left, left + range, null, fixedMax);
  25568. chart.xAxis[0].setExtremes(
  25569. Math.min(ext.min, ext.max),
  25570. Math.max(ext.min, ext.max),
  25571. true,
  25572. null, // auto animation
  25573. {
  25574. trigger: 'navigator'
  25575. }
  25576. );
  25577. }
  25578. }
  25579. },
  25580. /**
  25581. * Mousedown on a handle mask.
  25582. * Will store necessary information for drag&drop.
  25583. *
  25584. * @param {Object} e Mouse event
  25585. * @param {Number} index Index of a handle in Navigator.handles array
  25586. */
  25587. handlesMousedown: function(e, index) {
  25588. e = this.chart.pointer.normalize(e);
  25589. var navigator = this,
  25590. chart = navigator.chart,
  25591. baseXAxis = chart.xAxis[0],
  25592. // For reversed axes, min and max are chagned,
  25593. // so the other extreme should be stored
  25594. reverse = (chart.inverted && !baseXAxis.reversed) ||
  25595. (!chart.inverted && baseXAxis.reversed);
  25596. if (index === 0) {
  25597. // Grab the left handle
  25598. navigator.grabbedLeft = true;
  25599. navigator.otherHandlePos = navigator.zoomedMax;
  25600. navigator.fixedExtreme = reverse ? baseXAxis.min : baseXAxis.max;
  25601. } else {
  25602. // Grab the right handle
  25603. navigator.grabbedRight = true;
  25604. navigator.otherHandlePos = navigator.zoomedMin;
  25605. navigator.fixedExtreme = reverse ? baseXAxis.max : baseXAxis.min;
  25606. }
  25607. chart.fixedRange = null;
  25608. },
  25609. /**
  25610. * Mouse move event based on x/y mouse position.
  25611. * @param {Object} e Mouse event
  25612. */
  25613. onMouseMove: function(e) {
  25614. var navigator = this,
  25615. chart = navigator.chart,
  25616. left = navigator.left,
  25617. navigatorSize = navigator.navigatorSize,
  25618. range = navigator.range,
  25619. dragOffset = navigator.dragOffset,
  25620. inverted = chart.inverted,
  25621. chartX;
  25622. // In iOS, a mousemove event with e.pageX === 0 is fired when holding the finger
  25623. // down in the center of the scrollbar. This should be ignored.
  25624. if (!e.touches || e.touches[0].pageX !== 0) { // #4696, scrollbar failed on Android
  25625. e = chart.pointer.normalize(e);
  25626. chartX = e.chartX;
  25627. // Swap some options for inverted chart
  25628. if (inverted) {
  25629. left = navigator.top;
  25630. chartX = e.chartY;
  25631. }
  25632. // Drag left handle or top handle
  25633. if (navigator.grabbedLeft) {
  25634. navigator.hasDragged = true;
  25635. navigator.render(
  25636. 0,
  25637. 0,
  25638. chartX - left,
  25639. navigator.otherHandlePos
  25640. );
  25641. // Drag right handle or bottom handle
  25642. } else if (navigator.grabbedRight) {
  25643. navigator.hasDragged = true;
  25644. navigator.render(
  25645. 0,
  25646. 0,
  25647. navigator.otherHandlePos,
  25648. chartX - left
  25649. );
  25650. // Drag scrollbar or open area in navigator
  25651. } else if (navigator.grabbedCenter) {
  25652. navigator.hasDragged = true;
  25653. if (chartX < dragOffset) { // outside left
  25654. chartX = dragOffset;
  25655. } else if (chartX > navigatorSize + dragOffset - range) { // outside right
  25656. chartX = navigatorSize + dragOffset - range;
  25657. }
  25658. navigator.render(
  25659. 0,
  25660. 0,
  25661. chartX - dragOffset,
  25662. chartX - dragOffset + range
  25663. );
  25664. }
  25665. if (navigator.hasDragged && navigator.scrollbar && navigator.scrollbar.options.liveRedraw) {
  25666. e.DOMType = e.type; // DOMType is for IE8 because it can't read type async
  25667. setTimeout(function() {
  25668. navigator.onMouseUp(e);
  25669. }, 0);
  25670. }
  25671. }
  25672. },
  25673. /**
  25674. * Mouse up event based on x/y mouse position.
  25675. * @param {Object} e Mouse event
  25676. */
  25677. onMouseUp: function(e) {
  25678. var navigator = this,
  25679. chart = navigator.chart,
  25680. xAxis = navigator.xAxis,
  25681. scrollbar = navigator.scrollbar,
  25682. fixedMin,
  25683. fixedMax,
  25684. ext,
  25685. DOMEvent = e.DOMEvent || e;
  25686. if (
  25687. // MouseUp is called for both, navigator and scrollbar (that order),
  25688. // which causes calling afterSetExtremes twice. Prevent first call
  25689. // by checking if scrollbar is going to set new extremes (#6334)
  25690. (navigator.hasDragged && (!scrollbar || !scrollbar.hasDragged)) ||
  25691. e.trigger === 'scrollbar'
  25692. ) {
  25693. // When dragging one handle, make sure the other one doesn't change
  25694. if (navigator.zoomedMin === navigator.otherHandlePos) {
  25695. fixedMin = navigator.fixedExtreme;
  25696. } else if (navigator.zoomedMax === navigator.otherHandlePos) {
  25697. fixedMax = navigator.fixedExtreme;
  25698. }
  25699. // Snap to right edge (#4076)
  25700. if (navigator.zoomedMax === navigator.size) {
  25701. fixedMax = navigator.getUnionExtremes().dataMax;
  25702. }
  25703. ext = xAxis.toFixedRange(
  25704. navigator.zoomedMin,
  25705. navigator.zoomedMax,
  25706. fixedMin,
  25707. fixedMax
  25708. );
  25709. if (defined(ext.min)) {
  25710. chart.xAxis[0].setExtremes(
  25711. Math.min(ext.min, ext.max),
  25712. Math.max(ext.min, ext.max),
  25713. true,
  25714. navigator.hasDragged ? false : null, // Run animation when clicking buttons, scrollbar track etc, but not when dragging handles or scrollbar
  25715. {
  25716. trigger: 'navigator',
  25717. triggerOp: 'navigator-drag',
  25718. DOMEvent: DOMEvent // #1838
  25719. }
  25720. );
  25721. }
  25722. }
  25723. if (e.DOMType !== 'mousemove') {
  25724. navigator.grabbedLeft = navigator.grabbedRight =
  25725. navigator.grabbedCenter = navigator.fixedWidth =
  25726. navigator.fixedExtreme = navigator.otherHandlePos =
  25727. navigator.hasDragged = navigator.dragOffset = null;
  25728. }
  25729. },
  25730. /**
  25731. * Removes the event handlers attached previously with addEvents.
  25732. */
  25733. removeEvents: function() {
  25734. if (this.eventsToUnbind) {
  25735. each(this.eventsToUnbind, function(unbind) {
  25736. unbind();
  25737. });
  25738. this.eventsToUnbind = undefined;
  25739. }
  25740. this.removeBaseSeriesEvents();
  25741. },
  25742. /**
  25743. * Remove data events.
  25744. */
  25745. removeBaseSeriesEvents: function() {
  25746. var baseSeries = this.baseSeries || [];
  25747. if (this.navigatorEnabled && baseSeries[0] && this.navigatorOptions.adaptToUpdatedData !== false) {
  25748. each(baseSeries, function(series) {
  25749. removeEvent(series, 'updatedData', this.updatedDataHandler);
  25750. }, this);
  25751. // We only listen for extremes-events on the first baseSeries
  25752. if (baseSeries[0].xAxis) {
  25753. removeEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes);
  25754. }
  25755. }
  25756. },
  25757. /**
  25758. * Initiate the Navigator object
  25759. */
  25760. init: function(chart) {
  25761. var chartOptions = chart.options,
  25762. navigatorOptions = chartOptions.navigator,
  25763. navigatorEnabled = navigatorOptions.enabled,
  25764. scrollbarOptions = chartOptions.scrollbar,
  25765. scrollbarEnabled = scrollbarOptions.enabled,
  25766. height = navigatorEnabled ? navigatorOptions.height : 0,
  25767. scrollbarHeight = scrollbarEnabled ? scrollbarOptions.height : 0;
  25768. this.handles = [];
  25769. this.shades = [];
  25770. this.chart = chart;
  25771. this.setBaseSeries();
  25772. this.height = height;
  25773. this.scrollbarHeight = scrollbarHeight;
  25774. this.scrollbarEnabled = scrollbarEnabled;
  25775. this.navigatorEnabled = navigatorEnabled;
  25776. this.navigatorOptions = navigatorOptions;
  25777. this.scrollbarOptions = scrollbarOptions;
  25778. this.outlineHeight = height + scrollbarHeight;
  25779. this.opposite = pick(navigatorOptions.opposite, !navigatorEnabled && chart.inverted); // #6262
  25780. var navigator = this,
  25781. baseSeries = navigator.baseSeries,
  25782. xAxisIndex = chart.xAxis.length,
  25783. yAxisIndex = chart.yAxis.length,
  25784. baseXaxis = baseSeries && baseSeries[0] && baseSeries[0].xAxis || chart.xAxis[0];
  25785. // Make room for the navigator, can be placed around the chart:
  25786. chart.extraMargin = {
  25787. type: navigator.opposite ? 'plotTop' : 'marginBottom',
  25788. value: (navigatorEnabled || !chart.inverted ? navigator.outlineHeight : 0) + navigatorOptions.margin
  25789. };
  25790. if (chart.inverted) {
  25791. chart.extraMargin.type = navigator.opposite ? 'marginRight' : 'plotLeft';
  25792. }
  25793. chart.isDirtyBox = true;
  25794. if (navigator.navigatorEnabled) {
  25795. // an x axis is required for scrollbar also
  25796. navigator.xAxis = new Axis(chart, merge({
  25797. // inherit base xAxis' break and ordinal options
  25798. breaks: baseXaxis.options.breaks,
  25799. ordinal: baseXaxis.options.ordinal
  25800. }, navigatorOptions.xAxis, {
  25801. id: 'navigator-x-axis',
  25802. yAxis: 'navigator-y-axis',
  25803. isX: true,
  25804. type: 'datetime',
  25805. index: xAxisIndex,
  25806. offset: 0,
  25807. keepOrdinalPadding: true, // #2436
  25808. startOnTick: false,
  25809. endOnTick: false,
  25810. minPadding: 0,
  25811. maxPadding: 0,
  25812. zoomEnabled: false
  25813. }, chart.inverted ? {
  25814. offsets: [scrollbarHeight, 0, -scrollbarHeight, 0],
  25815. width: height
  25816. } : {
  25817. offsets: [0, -scrollbarHeight, 0, scrollbarHeight],
  25818. height: height
  25819. }));
  25820. navigator.yAxis = new Axis(chart, merge(navigatorOptions.yAxis, {
  25821. id: 'navigator-y-axis',
  25822. alignTicks: false,
  25823. offset: 0,
  25824. index: yAxisIndex,
  25825. zoomEnabled: false
  25826. }, chart.inverted ? {
  25827. width: height
  25828. } : {
  25829. height: height
  25830. }));
  25831. // If we have a base series, initialize the navigator series
  25832. if (baseSeries || navigatorOptions.series.data) {
  25833. navigator.addBaseSeries();
  25834. // If not, set up an event to listen for added series
  25835. } else if (chart.series.length === 0) {
  25836. wrap(chart, 'redraw', function(proceed, animation) {
  25837. // We've got one, now add it as base and reset chart.redraw
  25838. if (chart.series.length > 0 && !navigator.series) {
  25839. navigator.setBaseSeries();
  25840. chart.redraw = proceed; // reset
  25841. }
  25842. proceed.call(chart, animation);
  25843. });
  25844. }
  25845. // Render items, so we can bind events to them:
  25846. navigator.renderElements();
  25847. // Add mouse events
  25848. navigator.addMouseEvents();
  25849. // in case of scrollbar only, fake an x axis to get translation
  25850. } else {
  25851. navigator.xAxis = {
  25852. translate: function(value, reverse) {
  25853. var axis = chart.xAxis[0],
  25854. ext = axis.getExtremes(),
  25855. scrollTrackWidth = axis.len - 2 * scrollbarHeight,
  25856. min = numExt('min', axis.options.min, ext.dataMin),
  25857. valueRange = numExt('max', axis.options.max, ext.dataMax) - min;
  25858. return reverse ?
  25859. // from pixel to value
  25860. (value * valueRange / scrollTrackWidth) + min :
  25861. // from value to pixel
  25862. scrollTrackWidth * (value - min) / valueRange;
  25863. },
  25864. toPixels: function(value) {
  25865. return this.translate(value);
  25866. },
  25867. toValue: function(value) {
  25868. return this.translate(value, true);
  25869. },
  25870. toFixedRange: Axis.prototype.toFixedRange,
  25871. fake: true
  25872. };
  25873. }
  25874. // Initialize the scrollbar
  25875. if (chart.options.scrollbar.enabled) {
  25876. chart.scrollbar = navigator.scrollbar = new Scrollbar(
  25877. chart.renderer,
  25878. merge(chart.options.scrollbar, {
  25879. margin: navigator.navigatorEnabled ? 0 : 10,
  25880. vertical: chart.inverted
  25881. }),
  25882. chart
  25883. );
  25884. addEvent(navigator.scrollbar, 'changed', function(e) {
  25885. var range = navigator.size,
  25886. to = range * this.to,
  25887. from = range * this.from;
  25888. navigator.hasDragged = navigator.scrollbar.hasDragged;
  25889. navigator.render(0, 0, from, to);
  25890. if (chart.options.scrollbar.liveRedraw || e.DOMType !== 'mousemove') {
  25891. setTimeout(function() {
  25892. navigator.onMouseUp(e);
  25893. });
  25894. }
  25895. });
  25896. }
  25897. // Add data events
  25898. navigator.addBaseSeriesEvents();
  25899. // Add redraw events
  25900. navigator.addChartEvents();
  25901. },
  25902. /**
  25903. * Get the union data extremes of the chart - the outer data extremes of the base
  25904. * X axis and the navigator axis.
  25905. * @param {boolean} returnFalseOnNoBaseSeries - as the param says.
  25906. */
  25907. getUnionExtremes: function(returnFalseOnNoBaseSeries) {
  25908. var baseAxis = this.chart.xAxis[0],
  25909. navAxis = this.xAxis,
  25910. navAxisOptions = navAxis.options,
  25911. baseAxisOptions = baseAxis.options,
  25912. ret;
  25913. if (!returnFalseOnNoBaseSeries || baseAxis.dataMin !== null) {
  25914. ret = {
  25915. dataMin: pick( // #4053
  25916. navAxisOptions && navAxisOptions.min,
  25917. numExt(
  25918. 'min',
  25919. baseAxisOptions.min,
  25920. baseAxis.dataMin,
  25921. navAxis.dataMin,
  25922. navAxis.min
  25923. )
  25924. ),
  25925. dataMax: pick(
  25926. navAxisOptions && navAxisOptions.max,
  25927. numExt(
  25928. 'max',
  25929. baseAxisOptions.max,
  25930. baseAxis.dataMax,
  25931. navAxis.dataMax,
  25932. navAxis.max
  25933. )
  25934. )
  25935. };
  25936. }
  25937. return ret;
  25938. },
  25939. /**
  25940. * Set the base series. With a bit of modification we should be able to make
  25941. * this an API method to be called from the outside
  25942. * @param {Object} baseSeriesOptions - series options for a navigator
  25943. */
  25944. setBaseSeries: function(baseSeriesOptions) {
  25945. var chart = this.chart,
  25946. baseSeries;
  25947. baseSeriesOptions = baseSeriesOptions || chart.options && chart.options.navigator.baseSeries || 0;
  25948. // If we're resetting, remove the existing series
  25949. if (this.series) {
  25950. this.removeBaseSeriesEvents();
  25951. each(this.series, function(s) {
  25952. s.destroy();
  25953. });
  25954. }
  25955. baseSeries = this.baseSeries = [];
  25956. // Iterate through series and add the ones that should be shown in navigator
  25957. each(chart.series || [], function(series, i) {
  25958. if (series.options.showInNavigator || (i === baseSeriesOptions || series.options.id === baseSeriesOptions) &&
  25959. series.options.showInNavigator !== false) {
  25960. baseSeries.push(series);
  25961. }
  25962. });
  25963. // When run after render, this.xAxis already exists
  25964. if (this.xAxis && !this.xAxis.fake) {
  25965. this.addBaseSeries();
  25966. }
  25967. },
  25968. /*
  25969. * Add base series to the navigator.
  25970. */
  25971. addBaseSeries: function() {
  25972. var navigator = this,
  25973. chart = navigator.chart,
  25974. navigatorSeries = navigator.series = [],
  25975. baseSeries = navigator.baseSeries,
  25976. baseOptions,
  25977. mergedNavSeriesOptions,
  25978. chartNavigatorOptions = navigator.navigatorOptions.series,
  25979. baseNavigatorOptions,
  25980. navSeriesMixin = {
  25981. enableMouseTracking: false,
  25982. index: null, // #6162
  25983. group: 'nav', // for columns
  25984. padXAxis: false,
  25985. xAxis: 'navigator-x-axis',
  25986. yAxis: 'navigator-y-axis',
  25987. showInLegend: false,
  25988. stacking: false, // #4823
  25989. isInternal: true,
  25990. visible: true
  25991. };
  25992. // Go through each base series and merge the options to create new series
  25993. if (baseSeries) {
  25994. each(baseSeries, function(base, i) {
  25995. navSeriesMixin.name = 'Navigator ' + (i + 1);
  25996. baseOptions = base.options || {};
  25997. baseNavigatorOptions = baseOptions.navigatorOptions || {};
  25998. mergedNavSeriesOptions = merge(baseOptions, navSeriesMixin, chartNavigatorOptions, baseNavigatorOptions);
  25999. // Merge data separately. Do a slice to avoid mutating the navigator options from base series (#4923).
  26000. var navigatorSeriesData = baseNavigatorOptions.data || chartNavigatorOptions.data;
  26001. navigator.hasNavigatorData = navigator.hasNavigatorData || !!navigatorSeriesData;
  26002. mergedNavSeriesOptions.data = navigatorSeriesData || baseOptions.data && baseOptions.data.slice(0);
  26003. // Add the series
  26004. base.navigatorSeries = chart.initSeries(mergedNavSeriesOptions);
  26005. navigatorSeries.push(base.navigatorSeries);
  26006. });
  26007. } else {
  26008. // No base series, build from mixin and chart wide options
  26009. mergedNavSeriesOptions = merge(chartNavigatorOptions, navSeriesMixin);
  26010. mergedNavSeriesOptions.data = chartNavigatorOptions.data;
  26011. navigator.hasNavigatorData = !!mergedNavSeriesOptions.data;
  26012. navigatorSeries.push(chart.initSeries(mergedNavSeriesOptions));
  26013. }
  26014. this.addBaseSeriesEvents();
  26015. },
  26016. /**
  26017. * Add data events.
  26018. * For example when main series is updated we need to recalculate extremes
  26019. */
  26020. addBaseSeriesEvents: function() {
  26021. var navigator = this,
  26022. baseSeries = navigator.baseSeries || [];
  26023. // Bind modified extremes event to first base's xAxis only. In event of > 1 base-xAxes, the navigator will ignore those.
  26024. if (baseSeries[0] && baseSeries[0].xAxis) {
  26025. addEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes);
  26026. }
  26027. if (this.navigatorOptions.adaptToUpdatedData !== false) {
  26028. // Respond to updated data in the base series.
  26029. // Abort if lazy-loading data from the server.
  26030. each(baseSeries, function(base) {
  26031. if (base.xAxis) {
  26032. addEvent(base, 'updatedData', this.updatedDataHandler);
  26033. }
  26034. // Handle series removal
  26035. addEvent(base, 'remove', function() {
  26036. if (this.navigatorSeries) {
  26037. erase(navigator.series, this.navigatorSeries);
  26038. this.navigatorSeries.remove(false);
  26039. delete this.navigatorSeries;
  26040. }
  26041. });
  26042. }, this);
  26043. }
  26044. },
  26045. /**
  26046. * Set the navigator x axis extremes to reflect the total. The navigator extremes
  26047. * should always be the extremes of the union of all series in the chart as
  26048. * well as the navigator series.
  26049. */
  26050. modifyNavigatorAxisExtremes: function() {
  26051. var xAxis = this.xAxis,
  26052. unionExtremes;
  26053. if (xAxis.getExtremes) {
  26054. unionExtremes = this.getUnionExtremes(true);
  26055. if (unionExtremes && (unionExtremes.dataMin !== xAxis.min || unionExtremes.dataMax !== xAxis.max)) {
  26056. xAxis.min = unionExtremes.dataMin;
  26057. xAxis.max = unionExtremes.dataMax;
  26058. }
  26059. }
  26060. },
  26061. /**
  26062. * Hook to modify the base axis extremes with information from the Navigator
  26063. */
  26064. modifyBaseAxisExtremes: function() {
  26065. var baseXAxis = this,
  26066. navigator = baseXAxis.chart.navigator,
  26067. baseExtremes = baseXAxis.getExtremes(),
  26068. baseMin = baseExtremes.min,
  26069. baseMax = baseExtremes.max,
  26070. baseDataMin = baseExtremes.dataMin,
  26071. baseDataMax = baseExtremes.dataMax,
  26072. range = baseMax - baseMin,
  26073. stickToMin = navigator.stickToMin,
  26074. stickToMax = navigator.stickToMax,
  26075. newMax,
  26076. newMin,
  26077. navigatorSeries = navigator.series && navigator.series[0],
  26078. hasSetExtremes = !!baseXAxis.setExtremes,
  26079. // When the extremes have been set by range selector button, don't stick to min or max.
  26080. // The range selector buttons will handle the extremes. (#5489)
  26081. unmutable = baseXAxis.eventArgs && baseXAxis.eventArgs.trigger === 'rangeSelectorButton';
  26082. if (!unmutable) {
  26083. // If the zoomed range is already at the min, move it to the right as new data
  26084. // comes in
  26085. if (stickToMin) {
  26086. newMin = baseDataMin;
  26087. newMax = newMin + range;
  26088. }
  26089. // If the zoomed range is already at the max, move it to the right as new data
  26090. // comes in
  26091. if (stickToMax) {
  26092. newMax = baseDataMax;
  26093. if (!stickToMin) { // if stickToMin is true, the new min value is set above
  26094. newMin = Math.max(
  26095. newMax - range,
  26096. navigatorSeries && navigatorSeries.xData ?
  26097. navigatorSeries.xData[0] : -Number.MAX_VALUE
  26098. );
  26099. }
  26100. }
  26101. // Update the extremes
  26102. if (hasSetExtremes && (stickToMin || stickToMax)) {
  26103. if (isNumber(newMin)) {
  26104. baseXAxis.min = baseXAxis.userMin = newMin;
  26105. baseXAxis.max = baseXAxis.userMax = newMax;
  26106. }
  26107. }
  26108. }
  26109. // Reset
  26110. navigator.stickToMin = navigator.stickToMax = null;
  26111. },
  26112. /**
  26113. * Handler for updated data on the base series. When data is modified, the navigator series
  26114. * must reflect it. This is called from the Chart.redraw function before axis and series
  26115. * extremes are computed.
  26116. */
  26117. updatedDataHandler: function() {
  26118. var navigator = this.chart.navigator,
  26119. baseSeries = this,
  26120. navigatorSeries = this.navigatorSeries;
  26121. // Detect whether the zoomed area should stick to the minimum or maximum. If the current
  26122. // axis minimum falls outside the new updated dataset, we must adjust.
  26123. navigator.stickToMin = isNumber(baseSeries.xAxis.min) && (baseSeries.xAxis.min <= baseSeries.xData[0]);
  26124. // If the scrollbar is scrolled all the way to the right, keep right as new data
  26125. // comes in.
  26126. navigator.stickToMax = Math.round(navigator.zoomedMax) >= Math.round(navigator.size);
  26127. // Set the navigator series data to the new data of the base series
  26128. if (navigatorSeries && !navigator.hasNavigatorData) {
  26129. navigatorSeries.options.pointStart = baseSeries.xData[0];
  26130. navigatorSeries.setData(baseSeries.options.data, false, null, false); // #5414
  26131. }
  26132. },
  26133. /**
  26134. * Add chart events, like redrawing navigator, when chart requires that.
  26135. */
  26136. addChartEvents: function() {
  26137. addEvent(this.chart, 'redraw', function() {
  26138. // Move the scrollbar after redraw, like after data updata even if axes don't redraw
  26139. var navigator = this.navigator,
  26140. xAxis = navigator && (
  26141. navigator.baseSeries &&
  26142. navigator.baseSeries[0] &&
  26143. navigator.baseSeries[0].xAxis ||
  26144. navigator.scrollbar && this.xAxis[0]
  26145. ); // #5709
  26146. if (xAxis) {
  26147. navigator.render(xAxis.min, xAxis.max);
  26148. }
  26149. });
  26150. },
  26151. /**
  26152. * Destroys allocated elements.
  26153. */
  26154. destroy: function() {
  26155. // Disconnect events added in addEvents
  26156. this.removeEvents();
  26157. if (this.xAxis) {
  26158. erase(this.chart.xAxis, this.xAxis);
  26159. erase(this.chart.axes, this.xAxis);
  26160. }
  26161. if (this.yAxis) {
  26162. erase(this.chart.yAxis, this.yAxis);
  26163. erase(this.chart.axes, this.yAxis);
  26164. }
  26165. // Destroy series
  26166. each(this.series || [], function(s) {
  26167. if (s.destroy) {
  26168. s.destroy();
  26169. }
  26170. });
  26171. // Destroy properties
  26172. each([
  26173. 'series', 'xAxis', 'yAxis', 'shades', 'outline', 'scrollbarTrack',
  26174. 'scrollbarRifles', 'scrollbarGroup', 'scrollbar', 'navigatorGroup',
  26175. 'rendered'
  26176. ], function(prop) {
  26177. if (this[prop] && this[prop].destroy) {
  26178. this[prop].destroy();
  26179. }
  26180. this[prop] = null;
  26181. }, this);
  26182. // Destroy elements in collection
  26183. each([this.handles], function(coll) {
  26184. destroyObjectProperties(coll);
  26185. }, this);
  26186. }
  26187. };
  26188. H.Navigator = Navigator;
  26189. /**
  26190. * For Stock charts, override selection zooming with some special features because
  26191. * X axis zooming is already allowed by the Navigator and Range selector.
  26192. */
  26193. wrap(Axis.prototype, 'zoom', function(proceed, newMin, newMax) {
  26194. var chart = this.chart,
  26195. chartOptions = chart.options,
  26196. zoomType = chartOptions.chart.zoomType,
  26197. previousZoom,
  26198. navigator = chartOptions.navigator,
  26199. rangeSelector = chartOptions.rangeSelector,
  26200. ret;
  26201. if (this.isXAxis && ((navigator && navigator.enabled) ||
  26202. (rangeSelector && rangeSelector.enabled))) {
  26203. // For x only zooming, fool the chart.zoom method not to create the zoom button
  26204. // because the property already exists
  26205. if (zoomType === 'x') {
  26206. chart.resetZoomButton = 'blocked';
  26207. // For y only zooming, ignore the X axis completely
  26208. } else if (zoomType === 'y') {
  26209. ret = false;
  26210. // For xy zooming, record the state of the zoom before zoom selection, then when
  26211. // the reset button is pressed, revert to this state
  26212. } else if (zoomType === 'xy') {
  26213. previousZoom = this.previousZoom;
  26214. if (defined(newMin)) {
  26215. this.previousZoom = [this.min, this.max];
  26216. } else if (previousZoom) {
  26217. newMin = previousZoom[0];
  26218. newMax = previousZoom[1];
  26219. delete this.previousZoom;
  26220. }
  26221. }
  26222. }
  26223. return ret !== undefined ? ret : proceed.call(this, newMin, newMax);
  26224. });
  26225. // Initialize navigator for stock charts
  26226. wrap(Chart.prototype, 'init', function(proceed, options, callback) {
  26227. addEvent(this, 'beforeRender', function() {
  26228. var options = this.options;
  26229. if (options.navigator.enabled || options.scrollbar.enabled) {
  26230. this.scroller = this.navigator = new Navigator(this);
  26231. }
  26232. });
  26233. proceed.call(this, options, callback);
  26234. });
  26235. /**
  26236. * For stock charts, extend the Chart.setChartSize method so that we can set the final top position
  26237. * of the navigator once the height of the chart, including the legend, is determined. #367.
  26238. * We can't use Chart.getMargins, because labels offsets are not calculated yet.
  26239. */
  26240. wrap(Chart.prototype, 'setChartSize', function(proceed) {
  26241. var legend = this.legend,
  26242. navigator = this.navigator,
  26243. scrollbarHeight,
  26244. legendOptions,
  26245. xAxis,
  26246. yAxis;
  26247. proceed.apply(this, [].slice.call(arguments, 1));
  26248. if (navigator) {
  26249. legendOptions = legend.options;
  26250. xAxis = navigator.xAxis;
  26251. yAxis = navigator.yAxis;
  26252. scrollbarHeight = navigator.scrollbarHeight;
  26253. // Compute the top position
  26254. if (this.inverted) {
  26255. navigator.left = navigator.opposite ?
  26256. this.chartWidth - scrollbarHeight - navigator.height :
  26257. this.spacing[3] + scrollbarHeight;
  26258. navigator.top = this.plotTop + scrollbarHeight;
  26259. } else {
  26260. navigator.left = this.plotLeft + scrollbarHeight;
  26261. navigator.top = navigator.navigatorOptions.top ||
  26262. this.chartHeight - navigator.height - scrollbarHeight - this.spacing[2] -
  26263. (legendOptions.verticalAlign === 'bottom' && legendOptions.enabled && !legendOptions.floating ?
  26264. legend.legendHeight + pick(legendOptions.margin, 10) : 0);
  26265. }
  26266. if (xAxis && yAxis) { // false if navigator is disabled (#904)
  26267. if (this.inverted) {
  26268. xAxis.options.left = yAxis.options.left = navigator.left;
  26269. } else {
  26270. xAxis.options.top = yAxis.options.top = navigator.top;
  26271. }
  26272. xAxis.setAxisSize();
  26273. yAxis.setAxisSize();
  26274. }
  26275. }
  26276. });
  26277. // Pick up badly formatted point options to addPoint
  26278. wrap(Series.prototype, 'addPoint', function(proceed, options, redraw, shift, animation) {
  26279. var turboThreshold = this.options.turboThreshold;
  26280. if (turboThreshold && this.xData.length > turboThreshold && isObject(options, true) && this.chart.navigator) {
  26281. error(20, true);
  26282. }
  26283. proceed.call(this, options, redraw, shift, animation);
  26284. });
  26285. // Handle adding new series
  26286. wrap(Chart.prototype, 'addSeries', function(proceed, options, redraw, animation) {
  26287. var series = proceed.call(this, options, false, animation);
  26288. if (this.navigator) {
  26289. this.navigator.setBaseSeries(); // Recompute which series should be shown in navigator, and add them
  26290. }
  26291. if (pick(redraw, true)) {
  26292. this.redraw();
  26293. }
  26294. return series;
  26295. });
  26296. // Handle updating series
  26297. wrap(Series.prototype, 'update', function(proceed, newOptions, redraw) {
  26298. proceed.call(this, newOptions, false);
  26299. if (this.chart.navigator) {
  26300. this.chart.navigator.setBaseSeries();
  26301. }
  26302. if (pick(redraw, true)) {
  26303. this.chart.redraw();
  26304. }
  26305. });
  26306. Chart.prototype.callbacks.push(function(chart) {
  26307. var extremes,
  26308. navigator = chart.navigator;
  26309. // Initiate the navigator
  26310. if (navigator) {
  26311. extremes = chart.xAxis[0].getExtremes();
  26312. navigator.render(extremes.min, extremes.max);
  26313. }
  26314. });
  26315. /* ****************************************************************************
  26316. * End Navigator code *
  26317. *****************************************************************************/
  26318. }(Highcharts));
  26319. (function(H) {
  26320. /**
  26321. * (c) 2010-2017 Torstein Honsi
  26322. *
  26323. * License: www.highcharts.com/license
  26324. */
  26325. var addEvent = H.addEvent,
  26326. Axis = H.Axis,
  26327. Chart = H.Chart,
  26328. css = H.css,
  26329. createElement = H.createElement,
  26330. dateFormat = H.dateFormat,
  26331. defaultOptions = H.defaultOptions,
  26332. useUTC = defaultOptions.global.useUTC,
  26333. defined = H.defined,
  26334. destroyObjectProperties = H.destroyObjectProperties,
  26335. discardElement = H.discardElement,
  26336. each = H.each,
  26337. extend = H.extend,
  26338. fireEvent = H.fireEvent,
  26339. HCDate = H.Date,
  26340. isNumber = H.isNumber,
  26341. merge = H.merge,
  26342. pick = H.pick,
  26343. pInt = H.pInt,
  26344. splat = H.splat,
  26345. wrap = H.wrap;
  26346. /* ****************************************************************************
  26347. * Start Range Selector code *
  26348. *****************************************************************************/
  26349. extend(defaultOptions, {
  26350. rangeSelector: {
  26351. // allButtonsEnabled: false,
  26352. // enabled: true,
  26353. // buttons: {Object}
  26354. // buttonSpacing: 0,
  26355. buttonTheme: {
  26356. 'stroke-width': 0,
  26357. width: 28,
  26358. height: 18,
  26359. padding: 2,
  26360. zIndex: 7 // #484, #852
  26361. },
  26362. height: 35, // reserved space for buttons and input
  26363. inputPosition: {
  26364. align: 'right'
  26365. },
  26366. // inputDateFormat: '%b %e, %Y',
  26367. // inputEditDateFormat: '%Y-%m-%d',
  26368. // inputEnabled: true,
  26369. // selected: undefined,
  26370. // inputStyle: {},
  26371. labelStyle: {
  26372. color: '#666666'
  26373. }
  26374. }
  26375. });
  26376. defaultOptions.lang = merge(defaultOptions.lang, {
  26377. rangeSelectorZoom: 'Zoom',
  26378. rangeSelectorFrom: 'From',
  26379. rangeSelectorTo: 'To'
  26380. });
  26381. /**
  26382. * The range selector.
  26383. * @class
  26384. * @param {Object} chart
  26385. */
  26386. function RangeSelector(chart) {
  26387. // Run RangeSelector
  26388. this.init(chart);
  26389. }
  26390. RangeSelector.prototype = {
  26391. /**
  26392. * The method to run when one of the buttons in the range selectors is clicked
  26393. * @param {Number} i The index of the button
  26394. * @param {Object} rangeOptions
  26395. * @param {Boolean} redraw
  26396. */
  26397. clickButton: function(i, redraw) {
  26398. var rangeSelector = this,
  26399. chart = rangeSelector.chart,
  26400. rangeOptions = rangeSelector.buttonOptions[i],
  26401. baseAxis = chart.xAxis[0],
  26402. unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis || {},
  26403. dataMin = unionExtremes.dataMin,
  26404. dataMax = unionExtremes.dataMax,
  26405. newMin,
  26406. newMax = baseAxis && Math.round(Math.min(baseAxis.max, pick(dataMax, baseAxis.max))), // #1568
  26407. type = rangeOptions.type,
  26408. baseXAxisOptions,
  26409. range = rangeOptions._range,
  26410. rangeMin,
  26411. minSetting,
  26412. rangeSetting,
  26413. ctx,
  26414. ytdExtremes,
  26415. dataGrouping = rangeOptions.dataGrouping;
  26416. if (dataMin === null || dataMax === null) { // chart has no data, base series is removed
  26417. return;
  26418. }
  26419. // Set the fixed range before range is altered
  26420. chart.fixedRange = range;
  26421. // Apply dataGrouping associated to button
  26422. if (dataGrouping) {
  26423. this.forcedDataGrouping = true;
  26424. Axis.prototype.setDataGrouping.call(baseAxis || {
  26425. chart: this.chart
  26426. }, dataGrouping, false);
  26427. }
  26428. // Apply range
  26429. if (type === 'month' || type === 'year') {
  26430. if (!baseAxis) {
  26431. // This is set to the user options and picked up later when the axis is instantiated
  26432. // so that we know the min and max.
  26433. range = rangeOptions;
  26434. } else {
  26435. ctx = {
  26436. range: rangeOptions,
  26437. max: newMax,
  26438. dataMin: dataMin,
  26439. dataMax: dataMax
  26440. };
  26441. newMin = baseAxis.minFromRange.call(ctx);
  26442. if (isNumber(ctx.newMax)) {
  26443. newMax = ctx.newMax;
  26444. }
  26445. }
  26446. // Fixed times like minutes, hours, days
  26447. } else if (range) {
  26448. newMin = Math.max(newMax - range, dataMin);
  26449. newMax = Math.min(newMin + range, dataMax);
  26450. } else if (type === 'ytd') {
  26451. // On user clicks on the buttons, or a delayed action running from the beforeRender
  26452. // event (below), the baseAxis is defined.
  26453. if (baseAxis) {
  26454. // When "ytd" is the pre-selected button for the initial view, its calculation
  26455. // is delayed and rerun in the beforeRender event (below). When the series
  26456. // are initialized, but before the chart is rendered, we have access to the xData
  26457. // array (#942).
  26458. if (dataMax === undefined) {
  26459. dataMin = Number.MAX_VALUE;
  26460. dataMax = Number.MIN_VALUE;
  26461. each(chart.series, function(series) {
  26462. var xData = series.xData; // reassign it to the last item
  26463. dataMin = Math.min(xData[0], dataMin);
  26464. dataMax = Math.max(xData[xData.length - 1], dataMax);
  26465. });
  26466. redraw = false;
  26467. }
  26468. ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, useUTC);
  26469. newMin = rangeMin = ytdExtremes.min;
  26470. newMax = ytdExtremes.max;
  26471. // "ytd" is pre-selected. We don't yet have access to processed point and extremes data
  26472. // (things like pointStart and pointInterval are missing), so we delay the process (#942)
  26473. } else {
  26474. addEvent(chart, 'beforeRender', function() {
  26475. rangeSelector.clickButton(i);
  26476. });
  26477. return;
  26478. }
  26479. } else if (type === 'all' && baseAxis) {
  26480. newMin = dataMin;
  26481. newMax = dataMax;
  26482. }
  26483. rangeSelector.setSelected(i);
  26484. // Update the chart
  26485. if (!baseAxis) {
  26486. // Axis not yet instanciated. Temporarily set min and range
  26487. // options and remove them on chart load (#4317).
  26488. baseXAxisOptions = splat(chart.options.xAxis)[0];
  26489. rangeSetting = baseXAxisOptions.range;
  26490. baseXAxisOptions.range = range;
  26491. minSetting = baseXAxisOptions.min;
  26492. baseXAxisOptions.min = rangeMin;
  26493. addEvent(chart, 'load', function resetMinAndRange() {
  26494. baseXAxisOptions.range = rangeSetting;
  26495. baseXAxisOptions.min = minSetting;
  26496. });
  26497. } else {
  26498. // Existing axis object. Set extremes after render time.
  26499. baseAxis.setExtremes(
  26500. newMin,
  26501. newMax,
  26502. pick(redraw, 1),
  26503. null, // auto animation
  26504. {
  26505. trigger: 'rangeSelectorButton',
  26506. rangeSelectorButton: rangeOptions
  26507. }
  26508. );
  26509. }
  26510. },
  26511. /**
  26512. * Set the selected option. This method only sets the internal flag, it doesn't
  26513. * update the buttons or the actual zoomed range.
  26514. */
  26515. setSelected: function(selected) {
  26516. this.selected = this.options.selected = selected;
  26517. },
  26518. /**
  26519. * The default buttons for pre-selecting time frames
  26520. */
  26521. defaultButtons: [{
  26522. type: 'month',
  26523. count: 1,
  26524. text: '1m'
  26525. }, {
  26526. type: 'month',
  26527. count: 3,
  26528. text: '3m'
  26529. }, {
  26530. type: 'month',
  26531. count: 6,
  26532. text: '6m'
  26533. }, {
  26534. type: 'ytd',
  26535. text: 'YTD'
  26536. }, {
  26537. type: 'year',
  26538. count: 1,
  26539. text: '1y'
  26540. }, {
  26541. type: 'all',
  26542. text: 'All'
  26543. }],
  26544. /**
  26545. * Initialize the range selector
  26546. */
  26547. init: function(chart) {
  26548. var rangeSelector = this,
  26549. options = chart.options.rangeSelector,
  26550. buttonOptions = options.buttons || [].concat(rangeSelector.defaultButtons),
  26551. selectedOption = options.selected,
  26552. blurInputs = function() {
  26553. var minInput = rangeSelector.minInput,
  26554. maxInput = rangeSelector.maxInput;
  26555. if (minInput && minInput.blur) { //#3274 in some case blur is not defined
  26556. fireEvent(minInput, 'blur'); //#3274
  26557. }
  26558. if (maxInput && maxInput.blur) { //#3274 in some case blur is not defined
  26559. fireEvent(maxInput, 'blur'); //#3274
  26560. }
  26561. };
  26562. rangeSelector.chart = chart;
  26563. rangeSelector.options = options;
  26564. rangeSelector.buttons = [];
  26565. chart.extraTopMargin = options.height;
  26566. rangeSelector.buttonOptions = buttonOptions;
  26567. this.unMouseDown = addEvent(chart.container, 'mousedown', blurInputs);
  26568. this.unResize = addEvent(chart, 'resize', blurInputs);
  26569. // Extend the buttonOptions with actual range
  26570. each(buttonOptions, rangeSelector.computeButtonRange);
  26571. // zoomed range based on a pre-selected button index
  26572. if (selectedOption !== undefined && buttonOptions[selectedOption]) {
  26573. this.clickButton(selectedOption, false);
  26574. }
  26575. addEvent(chart, 'load', function() {
  26576. // If a data grouping is applied to the current button, release it when extremes change
  26577. addEvent(chart.xAxis[0], 'setExtremes', function(e) {
  26578. if (this.max - this.min !== chart.fixedRange && e.trigger !== 'rangeSelectorButton' &&
  26579. e.trigger !== 'updatedData' && rangeSelector.forcedDataGrouping) {
  26580. this.setDataGrouping(false, false);
  26581. }
  26582. });
  26583. });
  26584. },
  26585. /**
  26586. * Dynamically update the range selector buttons after a new range has been set
  26587. */
  26588. updateButtonStates: function() {
  26589. var rangeSelector = this,
  26590. chart = this.chart,
  26591. baseAxis = chart.xAxis[0],
  26592. actualRange = Math.round(baseAxis.max - baseAxis.min),
  26593. hasNoData = !baseAxis.hasVisibleSeries,
  26594. day = 24 * 36e5, // A single day in milliseconds
  26595. unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis,
  26596. dataMin = unionExtremes.dataMin,
  26597. dataMax = unionExtremes.dataMax,
  26598. ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, useUTC),
  26599. ytdMin = ytdExtremes.min,
  26600. ytdMax = ytdExtremes.max,
  26601. selected = rangeSelector.selected,
  26602. selectedExists = isNumber(selected),
  26603. allButtonsEnabled = rangeSelector.options.allButtonsEnabled,
  26604. buttons = rangeSelector.buttons;
  26605. each(rangeSelector.buttonOptions, function(rangeOptions, i) {
  26606. var range = rangeOptions._range,
  26607. type = rangeOptions.type,
  26608. count = rangeOptions.count || 1,
  26609. button = buttons[i],
  26610. state = 0,
  26611. disable,
  26612. select,
  26613. isSelected = i === selected,
  26614. // Disable buttons where the range exceeds what is allowed in the current view
  26615. isTooGreatRange = range > dataMax - dataMin,
  26616. // Disable buttons where the range is smaller than the minimum range
  26617. isTooSmallRange = range < baseAxis.minRange,
  26618. // Do not select the YTD button if not explicitly told so
  26619. isYTDButNotSelected = false,
  26620. // Disable the All button if we're already showing all
  26621. isAllButAlreadyShowingAll = false,
  26622. isSameRange = range === actualRange;
  26623. // Months and years have a variable range so we check the extremes
  26624. if (
  26625. (type === 'month' || type === 'year') &&
  26626. (actualRange >= {
  26627. month: 28,
  26628. year: 365
  26629. }[type] * day * count) &&
  26630. (actualRange <= {
  26631. month: 31,
  26632. year: 366
  26633. }[type] * day * count)
  26634. ) {
  26635. isSameRange = true;
  26636. } else if (type === 'ytd') {
  26637. isSameRange = (ytdMax - ytdMin) === actualRange;
  26638. isYTDButNotSelected = !isSelected;
  26639. } else if (type === 'all') {
  26640. isSameRange = baseAxis.max - baseAxis.min >= dataMax - dataMin;
  26641. isAllButAlreadyShowingAll = !isSelected && selectedExists && isSameRange;
  26642. }
  26643. // The new zoom area happens to match the range for a button - mark it selected.
  26644. // This happens when scrolling across an ordinal gap. It can be seen in the intraday
  26645. // demos when selecting 1h and scroll across the night gap.
  26646. disable = (!allButtonsEnabled &&
  26647. (
  26648. isTooGreatRange ||
  26649. isTooSmallRange ||
  26650. isAllButAlreadyShowingAll ||
  26651. hasNoData
  26652. )
  26653. );
  26654. select = (
  26655. (isSelected && isSameRange) ||
  26656. (isSameRange && !selectedExists && !isYTDButNotSelected)
  26657. );
  26658. if (disable) {
  26659. state = 3;
  26660. } else if (select) {
  26661. selectedExists = true; // Only one button can be selected
  26662. state = 2;
  26663. }
  26664. // If state has changed, update the button
  26665. if (button.state !== state) {
  26666. button.setState(state);
  26667. }
  26668. });
  26669. },
  26670. /**
  26671. * Compute and cache the range for an individual button
  26672. */
  26673. computeButtonRange: function(rangeOptions) {
  26674. var type = rangeOptions.type,
  26675. count = rangeOptions.count || 1,
  26676. // these time intervals have a fixed number of milliseconds, as opposed
  26677. // to month, ytd and year
  26678. fixedTimes = {
  26679. millisecond: 1,
  26680. second: 1000,
  26681. minute: 60 * 1000,
  26682. hour: 3600 * 1000,
  26683. day: 24 * 3600 * 1000,
  26684. week: 7 * 24 * 3600 * 1000
  26685. };
  26686. // Store the range on the button object
  26687. if (fixedTimes[type]) {
  26688. rangeOptions._range = fixedTimes[type] * count;
  26689. } else if (type === 'month' || type === 'year') {
  26690. rangeOptions._range = {
  26691. month: 30,
  26692. year: 365
  26693. }[type] * 24 * 36e5 * count;
  26694. }
  26695. },
  26696. /**
  26697. * Set the internal and displayed value of a HTML input for the dates
  26698. * @param {String} name
  26699. * @param {Number} time
  26700. */
  26701. setInputValue: function(name, time) {
  26702. var options = this.chart.options.rangeSelector,
  26703. input = this[name + 'Input'];
  26704. if (defined(time)) {
  26705. input.previousValue = input.HCTime;
  26706. input.HCTime = time;
  26707. }
  26708. input.value = dateFormat(
  26709. options.inputEditDateFormat || '%Y-%m-%d',
  26710. input.HCTime
  26711. );
  26712. this[name + 'DateBox'].attr({
  26713. text: dateFormat(options.inputDateFormat || '%b %e, %Y', input.HCTime)
  26714. });
  26715. },
  26716. showInput: function(name) {
  26717. var inputGroup = this.inputGroup,
  26718. dateBox = this[name + 'DateBox'];
  26719. css(this[name + 'Input'], {
  26720. left: (inputGroup.translateX + dateBox.x) + 'px',
  26721. top: inputGroup.translateY + 'px',
  26722. width: (dateBox.width - 2) + 'px',
  26723. height: (dateBox.height - 2) + 'px',
  26724. border: '2px solid silver'
  26725. });
  26726. },
  26727. hideInput: function(name) {
  26728. css(this[name + 'Input'], {
  26729. border: 0,
  26730. width: '1px',
  26731. height: '1px'
  26732. });
  26733. this.setInputValue(name);
  26734. },
  26735. /**
  26736. * Draw either the 'from' or the 'to' HTML input box of the range selector
  26737. * @param {Object} name
  26738. */
  26739. drawInput: function(name) {
  26740. var rangeSelector = this,
  26741. chart = rangeSelector.chart,
  26742. chartStyle = chart.renderer.style || {},
  26743. renderer = chart.renderer,
  26744. options = chart.options.rangeSelector,
  26745. lang = defaultOptions.lang,
  26746. div = rangeSelector.div,
  26747. isMin = name === 'min',
  26748. input,
  26749. label,
  26750. dateBox,
  26751. inputGroup = this.inputGroup;
  26752. function updateExtremes() {
  26753. var inputValue = input.value,
  26754. value = (options.inputDateParser || Date.parse)(inputValue),
  26755. chartAxis = chart.xAxis[0],
  26756. dataAxis = chart.scroller && chart.scroller.xAxis ? chart.scroller.xAxis : chartAxis,
  26757. dataMin = dataAxis.dataMin,
  26758. dataMax = dataAxis.dataMax;
  26759. if (value !== input.previousValue) {
  26760. input.previousValue = value;
  26761. // If the value isn't parsed directly to a value by the browser's Date.parse method,
  26762. // like YYYY-MM-DD in IE, try parsing it a different way
  26763. if (!isNumber(value)) {
  26764. value = inputValue.split('-');
  26765. value = Date.UTC(pInt(value[0]), pInt(value[1]) - 1, pInt(value[2]));
  26766. }
  26767. if (isNumber(value)) {
  26768. // Correct for timezone offset (#433)
  26769. if (!useUTC) {
  26770. value = value + new Date().getTimezoneOffset() * 60 * 1000;
  26771. }
  26772. // Validate the extremes. If it goes beyound the data min or max, use the
  26773. // actual data extreme (#2438).
  26774. if (isMin) {
  26775. if (value > rangeSelector.maxInput.HCTime) {
  26776. value = undefined;
  26777. } else if (value < dataMin) {
  26778. value = dataMin;
  26779. }
  26780. } else {
  26781. if (value < rangeSelector.minInput.HCTime) {
  26782. value = undefined;
  26783. } else if (value > dataMax) {
  26784. value = dataMax;
  26785. }
  26786. }
  26787. // Set the extremes
  26788. if (value !== undefined) {
  26789. chartAxis.setExtremes(
  26790. isMin ? value : chartAxis.min,
  26791. isMin ? chartAxis.max : value,
  26792. undefined,
  26793. undefined, {
  26794. trigger: 'rangeSelectorInput'
  26795. }
  26796. );
  26797. }
  26798. }
  26799. }
  26800. }
  26801. // Create the text label
  26802. this[name + 'Label'] = label = renderer.label(lang[isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo'], this.inputGroup.offset)
  26803. .addClass('highcharts-range-label')
  26804. .attr({
  26805. padding: 2
  26806. })
  26807. .add(inputGroup);
  26808. inputGroup.offset += label.width + 5;
  26809. // Create an SVG label that shows updated date ranges and and records click events that
  26810. // bring in the HTML input.
  26811. this[name + 'DateBox'] = dateBox = renderer.label('', inputGroup.offset)
  26812. .addClass('highcharts-range-input')
  26813. .attr({
  26814. padding: 2,
  26815. width: options.inputBoxWidth || 90,
  26816. height: options.inputBoxHeight || 17,
  26817. stroke: options.inputBoxBorderColor || '#cccccc',
  26818. 'stroke-width': 1,
  26819. 'text-align': 'center'
  26820. })
  26821. .on('click', function() {
  26822. rangeSelector.showInput(name); // If it is already focused, the onfocus event doesn't fire (#3713)
  26823. rangeSelector[name + 'Input'].focus();
  26824. })
  26825. .add(inputGroup);
  26826. inputGroup.offset += dateBox.width + (isMin ? 10 : 0);
  26827. // Create the HTML input element. This is rendered as 1x1 pixel then set to the right size
  26828. // when focused.
  26829. this[name + 'Input'] = input = createElement('input', {
  26830. name: name,
  26831. className: 'highcharts-range-selector',
  26832. type: 'text'
  26833. }, {
  26834. top: chart.plotTop + 'px' // prevent jump on focus in Firefox
  26835. }, div);
  26836. // Styles
  26837. label.css(merge(chartStyle, options.labelStyle));
  26838. dateBox.css(merge({
  26839. color: '#333333'
  26840. }, chartStyle, options.inputStyle));
  26841. css(input, extend({
  26842. position: 'absolute',
  26843. border: 0,
  26844. width: '1px', // Chrome needs a pixel to see it
  26845. height: '1px',
  26846. padding: 0,
  26847. textAlign: 'center',
  26848. fontSize: chartStyle.fontSize,
  26849. fontFamily: chartStyle.fontFamily,
  26850. left: '-9em' // #4798
  26851. }, options.inputStyle));
  26852. // Blow up the input box
  26853. input.onfocus = function() {
  26854. rangeSelector.showInput(name);
  26855. };
  26856. // Hide away the input box
  26857. input.onblur = function() {
  26858. rangeSelector.hideInput(name);
  26859. };
  26860. // handle changes in the input boxes
  26861. input.onchange = updateExtremes;
  26862. input.onkeypress = function(event) {
  26863. // IE does not fire onchange on enter
  26864. if (event.keyCode === 13) {
  26865. updateExtremes();
  26866. }
  26867. };
  26868. },
  26869. /**
  26870. * Get the position of the range selector buttons and inputs. This can be overridden from outside for custom positioning.
  26871. */
  26872. getPosition: function() {
  26873. var chart = this.chart,
  26874. options = chart.options.rangeSelector,
  26875. buttonTop = pick((options.buttonPosition || {}).y, chart.plotTop - chart.axisOffset[0] - options.height);
  26876. return {
  26877. buttonTop: buttonTop,
  26878. inputTop: buttonTop - 10
  26879. };
  26880. },
  26881. /**
  26882. * Get the extremes of YTD.
  26883. * Will choose dataMax if its value is lower than the current timestamp.
  26884. * Will choose dataMin if its value is higher than the timestamp for
  26885. * the start of current year.
  26886. * @param {number} dataMax
  26887. * @param {number} dataMin
  26888. * @return {object} Returns min and max for the YTD
  26889. */
  26890. getYTDExtremes: function(dataMax, dataMin, useUTC) {
  26891. var min,
  26892. now = new HCDate(dataMax),
  26893. year = now[HCDate.hcGetFullYear](),
  26894. startOfYear = useUTC ? HCDate.UTC(year, 0, 1) : +new HCDate(year, 0, 1); // eslint-disable-line new-cap
  26895. min = Math.max(dataMin || 0, startOfYear);
  26896. now = now.getTime();
  26897. return {
  26898. max: Math.min(dataMax || now, now),
  26899. min: min
  26900. };
  26901. },
  26902. /**
  26903. * Render the range selector including the buttons and the inputs. The first time render
  26904. * is called, the elements are created and positioned. On subsequent calls, they are
  26905. * moved and updated.
  26906. * @param {Number} min X axis minimum
  26907. * @param {Number} max X axis maximum
  26908. */
  26909. render: function(min, max) {
  26910. var rangeSelector = this,
  26911. chart = rangeSelector.chart,
  26912. renderer = chart.renderer,
  26913. container = chart.container,
  26914. chartOptions = chart.options,
  26915. navButtonOptions = chartOptions.exporting && chartOptions.exporting.enabled !== false &&
  26916. chartOptions.navigation && chartOptions.navigation.buttonOptions,
  26917. options = chartOptions.rangeSelector,
  26918. buttons = rangeSelector.buttons,
  26919. lang = defaultOptions.lang,
  26920. div = rangeSelector.div,
  26921. inputGroup = rangeSelector.inputGroup,
  26922. buttonTheme = options.buttonTheme,
  26923. buttonPosition = options.buttonPosition || {},
  26924. inputEnabled = options.inputEnabled,
  26925. states = buttonTheme && buttonTheme.states,
  26926. plotLeft = chart.plotLeft,
  26927. buttonLeft,
  26928. pos = this.getPosition(),
  26929. buttonGroup = rangeSelector.group,
  26930. buttonBBox,
  26931. rendered = rangeSelector.rendered;
  26932. if (options.enabled === false) {
  26933. return;
  26934. }
  26935. // create the elements
  26936. if (!rendered) {
  26937. rangeSelector.group = buttonGroup = renderer.g('range-selector-buttons').add();
  26938. rangeSelector.zoomText = renderer.text(lang.rangeSelectorZoom, pick(buttonPosition.x, plotLeft), 15)
  26939. .css(options.labelStyle)
  26940. .add(buttonGroup);
  26941. // button starting position
  26942. buttonLeft = pick(buttonPosition.x, plotLeft) + rangeSelector.zoomText.getBBox().width + 5;
  26943. each(rangeSelector.buttonOptions, function(rangeOptions, i) {
  26944. buttons[i] = renderer.button(
  26945. rangeOptions.text,
  26946. buttonLeft,
  26947. 0,
  26948. function() {
  26949. rangeSelector.clickButton(i);
  26950. rangeSelector.isActive = true;
  26951. },
  26952. buttonTheme,
  26953. states && states.hover,
  26954. states && states.select,
  26955. states && states.disabled
  26956. )
  26957. .attr({
  26958. 'text-align': 'center'
  26959. })
  26960. .add(buttonGroup);
  26961. // increase button position for the next button
  26962. buttonLeft += buttons[i].width + pick(options.buttonSpacing, 5);
  26963. });
  26964. // first create a wrapper outside the container in order to make
  26965. // the inputs work and make export correct
  26966. if (inputEnabled !== false) {
  26967. rangeSelector.div = div = createElement('div', null, {
  26968. position: 'relative',
  26969. height: 0,
  26970. zIndex: 1 // above container
  26971. });
  26972. container.parentNode.insertBefore(div, container);
  26973. // Create the group to keep the inputs
  26974. rangeSelector.inputGroup = inputGroup = renderer.g('input-group')
  26975. .add();
  26976. inputGroup.offset = 0;
  26977. rangeSelector.drawInput('min');
  26978. rangeSelector.drawInput('max');
  26979. }
  26980. }
  26981. rangeSelector.updateButtonStates();
  26982. // Set or update the group position
  26983. buttonGroup[rendered ? 'animate' : 'attr']({
  26984. translateY: pos.buttonTop
  26985. });
  26986. if (inputEnabled !== false) {
  26987. // Update the alignment to the updated spacing box
  26988. inputGroup.align(extend({
  26989. y: pos.inputTop,
  26990. width: inputGroup.offset,
  26991. // Detect collision with the exporting buttons
  26992. x: navButtonOptions && (pos.inputTop < (navButtonOptions.y || 0) + navButtonOptions.height - chart.spacing[0]) ?
  26993. -40 : 0
  26994. }, options.inputPosition), true, chart.spacingBox);
  26995. // Hide if overlapping - inputEnabled is null or undefined
  26996. if (!defined(inputEnabled)) {
  26997. buttonBBox = buttonGroup.getBBox();
  26998. inputGroup[inputGroup.alignAttr.translateX < buttonBBox.x + buttonBBox.width + 10 ? 'hide' : 'show']();
  26999. }
  27000. // Set or reset the input values
  27001. rangeSelector.setInputValue('min', min);
  27002. rangeSelector.setInputValue('max', max);
  27003. }
  27004. rangeSelector.rendered = true;
  27005. },
  27006. /**
  27007. * Update the range selector with new options
  27008. */
  27009. update: function(options) {
  27010. var chart = this.chart;
  27011. merge(true, chart.options.rangeSelector, options);
  27012. this.destroy();
  27013. this.init(chart);
  27014. },
  27015. /**
  27016. * Destroys allocated elements.
  27017. */
  27018. destroy: function() {
  27019. var rSelector = this,
  27020. minInput = rSelector.minInput,
  27021. maxInput = rSelector.maxInput;
  27022. rSelector.unMouseDown();
  27023. rSelector.unResize();
  27024. // Destroy elements in collections
  27025. destroyObjectProperties(rSelector.buttons);
  27026. // Clear input element events
  27027. if (minInput) {
  27028. minInput.onfocus = minInput.onblur = minInput.onchange = null;
  27029. }
  27030. if (maxInput) {
  27031. maxInput.onfocus = maxInput.onblur = maxInput.onchange = null;
  27032. }
  27033. // Destroy HTML and SVG elements
  27034. H.objectEach(rSelector, function(val, key) {
  27035. if (val && key !== 'chart') {
  27036. if (val.destroy) { // SVGElement
  27037. val.destroy();
  27038. } else if (val.nodeType) { // HTML element
  27039. discardElement(this[key]);
  27040. }
  27041. }
  27042. if (val !== RangeSelector.prototype[key]) {
  27043. rSelector[key] = null;
  27044. }
  27045. }, this);
  27046. }
  27047. };
  27048. /**
  27049. * Add logic to normalize the zoomed range in order to preserve the pressed state of range selector buttons
  27050. */
  27051. Axis.prototype.toFixedRange = function(pxMin, pxMax, fixedMin, fixedMax) {
  27052. var fixedRange = this.chart && this.chart.fixedRange,
  27053. newMin = pick(fixedMin, this.translate(pxMin, true, !this.horiz)),
  27054. newMax = pick(fixedMax, this.translate(pxMax, true, !this.horiz)),
  27055. changeRatio = fixedRange && (newMax - newMin) / fixedRange;
  27056. // If the difference between the fixed range and the actual requested range is
  27057. // too great, the user is dragging across an ordinal gap, and we need to release
  27058. // the range selector button.
  27059. if (changeRatio > 0.7 && changeRatio < 1.3) {
  27060. if (fixedMax) {
  27061. newMin = newMax - fixedRange;
  27062. } else {
  27063. newMax = newMin + fixedRange;
  27064. }
  27065. }
  27066. if (!isNumber(newMin)) { // #1195
  27067. newMin = newMax = undefined;
  27068. }
  27069. return {
  27070. min: newMin,
  27071. max: newMax
  27072. };
  27073. };
  27074. /**
  27075. * Get the axis min value based on the range option and the current max. For
  27076. * stock charts this is extended via the {@link RangeSelector} so that if the
  27077. * selected range is a multiple of months or years, it is compensated for
  27078. * various month lengths.
  27079. *
  27080. * @return {number} The new minimum value.
  27081. */
  27082. Axis.prototype.minFromRange = function() {
  27083. var rangeOptions = this.range,
  27084. type = rangeOptions.type,
  27085. timeName = {
  27086. month: 'Month',
  27087. year: 'FullYear'
  27088. }[type],
  27089. min,
  27090. max = this.max,
  27091. dataMin,
  27092. range,
  27093. // Get the true range from a start date
  27094. getTrueRange = function(base, count) {
  27095. var date = new Date(base),
  27096. basePeriod = date['get' + timeName]();
  27097. date['set' + timeName](basePeriod + count);
  27098. if (basePeriod === date['get' + timeName]()) {
  27099. date.setDate(0); // #6537
  27100. }
  27101. return date.getTime() - base;
  27102. };
  27103. if (isNumber(rangeOptions)) {
  27104. min = max - rangeOptions;
  27105. range = rangeOptions;
  27106. } else {
  27107. min = max + getTrueRange(max, -rangeOptions.count);
  27108. // Let the fixedRange reflect initial settings (#5930)
  27109. if (this.chart) {
  27110. this.chart.fixedRange = max - min;
  27111. }
  27112. }
  27113. dataMin = pick(this.dataMin, Number.MIN_VALUE);
  27114. if (!isNumber(min)) {
  27115. min = dataMin;
  27116. }
  27117. if (min <= dataMin) {
  27118. min = dataMin;
  27119. if (range === undefined) { // #4501
  27120. range = getTrueRange(min, rangeOptions.count);
  27121. }
  27122. this.newMax = Math.min(min + range, this.dataMax);
  27123. }
  27124. if (!isNumber(max)) {
  27125. min = undefined;
  27126. }
  27127. return min;
  27128. };
  27129. // Initialize scroller for stock charts
  27130. wrap(Chart.prototype, 'init', function(proceed, options, callback) {
  27131. addEvent(this, 'init', function() {
  27132. if (this.options.rangeSelector.enabled) {
  27133. this.rangeSelector = new RangeSelector(this);
  27134. }
  27135. });
  27136. proceed.call(this, options, callback);
  27137. });
  27138. Chart.prototype.callbacks.push(function(chart) {
  27139. var extremes,
  27140. rangeSelector = chart.rangeSelector,
  27141. unbindRender,
  27142. unbindSetExtremes;
  27143. function renderRangeSelector() {
  27144. extremes = chart.xAxis[0].getExtremes();
  27145. if (isNumber(extremes.min)) {
  27146. rangeSelector.render(extremes.min, extremes.max);
  27147. }
  27148. }
  27149. if (rangeSelector) {
  27150. // redraw the scroller on setExtremes
  27151. unbindSetExtremes = addEvent(
  27152. chart.xAxis[0],
  27153. 'afterSetExtremes',
  27154. function(e) {
  27155. rangeSelector.render(e.min, e.max);
  27156. }
  27157. );
  27158. // redraw the scroller chart resize
  27159. unbindRender = addEvent(chart, 'redraw', renderRangeSelector);
  27160. // do it now
  27161. renderRangeSelector();
  27162. }
  27163. // Remove resize/afterSetExtremes at chart destroy
  27164. addEvent(chart, 'destroy', function destroyEvents() {
  27165. if (rangeSelector) {
  27166. unbindRender();
  27167. unbindSetExtremes();
  27168. }
  27169. });
  27170. });
  27171. H.RangeSelector = RangeSelector;
  27172. /* ****************************************************************************
  27173. * End Range Selector code *
  27174. *****************************************************************************/
  27175. }(Highcharts));
  27176. (function(H) {
  27177. /**
  27178. * (c) 2010-2017 Torstein Honsi
  27179. *
  27180. * License: www.highcharts.com/license
  27181. */
  27182. var arrayMax = H.arrayMax,
  27183. arrayMin = H.arrayMin,
  27184. Axis = H.Axis,
  27185. Chart = H.Chart,
  27186. defined = H.defined,
  27187. each = H.each,
  27188. extend = H.extend,
  27189. format = H.format,
  27190. grep = H.grep,
  27191. inArray = H.inArray,
  27192. isNumber = H.isNumber,
  27193. isString = H.isString,
  27194. map = H.map,
  27195. merge = H.merge,
  27196. pick = H.pick,
  27197. Point = H.Point,
  27198. Renderer = H.Renderer,
  27199. Series = H.Series,
  27200. splat = H.splat,
  27201. SVGRenderer = H.SVGRenderer,
  27202. VMLRenderer = H.VMLRenderer,
  27203. wrap = H.wrap,
  27204. seriesProto = Series.prototype,
  27205. seriesInit = seriesProto.init,
  27206. seriesProcessData = seriesProto.processData,
  27207. pointTooltipFormatter = Point.prototype.tooltipFormatter;
  27208. /**
  27209. * Factory function for creating new stock charts. Creates a new {@link Chart|
  27210. * Chart} object with different default options than the basic Chart.
  27211. *
  27212. * @function #stockChart
  27213. * @memberOf Highcharts
  27214. *
  27215. * @param {String|HTMLDOMElement} renderTo
  27216. * The DOM element to render to, or its id.
  27217. * @param {Options} options
  27218. * The chart options structure as described in the {@link
  27219. * https://api.highcharts.com/highstock|options reference}.
  27220. * @param {Function} callback
  27221. * A function to execute when the chart object is finished loading and
  27222. * rendering. In most cases the chart is built in one thread, but in
  27223. * Internet Explorer version 8 or less the chart is sometimes initiated
  27224. * before the document is ready, and in these cases the chart object
  27225. * will not be finished synchronously. As a consequence, code that
  27226. * relies on the newly built Chart object should always run in the
  27227. * callback. Defining a {@link https://api.highcharts.com/highstock/chart.events.load|
  27228. * chart.event.load} handler is equivalent.
  27229. *
  27230. * @return {Chart}
  27231. * The chart object.
  27232. *
  27233. * @example
  27234. * var chart = Highcharts.stockChart('container', {
  27235. * series: [{
  27236. * data: [1, 2, 3, 4, 5, 6, 7, 8, 9],
  27237. * pointInterval: 24 * 60 * 60 * 1000
  27238. * }]
  27239. * });
  27240. */
  27241. H.StockChart = H.stockChart = function(a, b, c) {
  27242. var hasRenderToArg = isString(a) || a.nodeName,
  27243. options = arguments[hasRenderToArg ? 1 : 0],
  27244. seriesOptions = options.series, // to increase performance, don't merge the data
  27245. defaultOptions = H.getOptions(),
  27246. opposite,
  27247. // Always disable startOnTick:true on the main axis when the navigator
  27248. // is enabled (#1090)
  27249. navigatorEnabled = pick(
  27250. options.navigator && options.navigator.enabled,
  27251. defaultOptions.navigator.enabled,
  27252. true
  27253. ),
  27254. disableStartOnTick = navigatorEnabled ? {
  27255. startOnTick: false,
  27256. endOnTick: false
  27257. } : null,
  27258. lineOptions = {
  27259. marker: {
  27260. enabled: false,
  27261. radius: 2
  27262. }
  27263. // gapSize: 0
  27264. },
  27265. columnOptions = {
  27266. shadow: false,
  27267. borderWidth: 0
  27268. };
  27269. // apply X axis options to both single and multi y axes
  27270. options.xAxis = map(splat(options.xAxis || {}), function(xAxisOptions) {
  27271. return merge({ // defaults
  27272. minPadding: 0,
  27273. maxPadding: 0,
  27274. ordinal: true,
  27275. title: {
  27276. text: null
  27277. },
  27278. labels: {
  27279. overflow: 'justify'
  27280. },
  27281. showLastLabel: true
  27282. },
  27283. defaultOptions.xAxis, // #3802
  27284. xAxisOptions, // user options
  27285. { // forced options
  27286. type: 'datetime',
  27287. categories: null
  27288. },
  27289. disableStartOnTick
  27290. );
  27291. });
  27292. // apply Y axis options to both single and multi y axes
  27293. options.yAxis = map(splat(options.yAxis || {}), function(yAxisOptions) {
  27294. opposite = pick(yAxisOptions.opposite, true);
  27295. return merge({ // defaults
  27296. labels: {
  27297. y: -2
  27298. },
  27299. opposite: opposite,
  27300. showLastLabel: false,
  27301. title: {
  27302. text: null
  27303. }
  27304. },
  27305. defaultOptions.yAxis, // #3802
  27306. yAxisOptions // user options
  27307. );
  27308. });
  27309. options.series = null;
  27310. options = merge({
  27311. chart: {
  27312. panning: true,
  27313. pinchType: 'x'
  27314. },
  27315. navigator: {
  27316. enabled: navigatorEnabled
  27317. },
  27318. scrollbar: {
  27319. // #4988 - check if setOptions was called
  27320. enabled: pick(defaultOptions.scrollbar.enabled, true)
  27321. },
  27322. rangeSelector: {
  27323. // #4988 - check if setOptions was called
  27324. enabled: pick(defaultOptions.rangeSelector.enabled, true)
  27325. },
  27326. title: {
  27327. text: null
  27328. },
  27329. tooltip: {
  27330. shared: true,
  27331. crosshairs: true
  27332. },
  27333. legend: {
  27334. enabled: false
  27335. },
  27336. plotOptions: {
  27337. line: lineOptions,
  27338. spline: lineOptions,
  27339. area: lineOptions,
  27340. areaspline: lineOptions,
  27341. arearange: lineOptions,
  27342. areasplinerange: lineOptions,
  27343. column: columnOptions,
  27344. columnrange: columnOptions,
  27345. candlestick: columnOptions,
  27346. ohlc: columnOptions
  27347. }
  27348. },
  27349. options, // user's options
  27350. { // forced options
  27351. isStock: true // internal flag
  27352. }
  27353. );
  27354. options.series = seriesOptions;
  27355. return hasRenderToArg ?
  27356. new Chart(a, options, c) :
  27357. new Chart(options, b);
  27358. };
  27359. // Override the automatic label alignment so that the first Y axis' labels
  27360. // are drawn on top of the grid line, and subsequent axes are drawn outside
  27361. wrap(Axis.prototype, 'autoLabelAlign', function(proceed) {
  27362. var chart = this.chart,
  27363. options = this.options,
  27364. panes = chart._labelPanes = chart._labelPanes || {},
  27365. key,
  27366. labelOptions = this.options.labels;
  27367. if (this.chart.options.isStock && this.coll === 'yAxis') {
  27368. key = options.top + ',' + options.height;
  27369. if (!panes[key] && labelOptions.enabled) { // do it only for the first Y axis of each pane
  27370. if (labelOptions.x === 15) { // default
  27371. labelOptions.x = 0;
  27372. }
  27373. if (labelOptions.align === undefined) {
  27374. labelOptions.align = 'right';
  27375. }
  27376. panes[key] = this;
  27377. return 'right';
  27378. }
  27379. }
  27380. return proceed.call(this, [].slice.call(arguments, 1));
  27381. });
  27382. // Clear axis from label panes (#6071)
  27383. wrap(Axis.prototype, 'destroy', function(proceed) {
  27384. var chart = this.chart,
  27385. key = this.options && (this.options.top + ',' + this.options.height);
  27386. if (key && chart._labelPanes && chart._labelPanes[key] === this) {
  27387. delete chart._labelPanes[key];
  27388. }
  27389. return proceed.call(this, Array.prototype.slice.call(arguments, 1));
  27390. });
  27391. // Override getPlotLinePath to allow for multipane charts
  27392. wrap(Axis.prototype, 'getPlotLinePath', function(proceed, value, lineWidth, old, force, translatedValue) {
  27393. var axis = this,
  27394. series = (this.isLinked && !this.series ? this.linkedParent.series : this.series),
  27395. chart = axis.chart,
  27396. renderer = chart.renderer,
  27397. axisLeft = axis.left,
  27398. axisTop = axis.top,
  27399. x1,
  27400. y1,
  27401. x2,
  27402. y2,
  27403. result = [],
  27404. axes = [], //#3416 need a default array
  27405. axes2,
  27406. uniqueAxes,
  27407. transVal;
  27408. /**
  27409. * Return the other axis based on either the axis option or on related series.
  27410. */
  27411. function getAxis(coll) {
  27412. var otherColl = coll === 'xAxis' ? 'yAxis' : 'xAxis',
  27413. opt = axis.options[otherColl];
  27414. // Other axis indexed by number
  27415. if (isNumber(opt)) {
  27416. return [chart[otherColl][opt]];
  27417. }
  27418. // Other axis indexed by id (like navigator)
  27419. if (isString(opt)) {
  27420. return [chart.get(opt)];
  27421. }
  27422. // Auto detect based on existing series
  27423. return map(series, function(s) {
  27424. return s[otherColl];
  27425. });
  27426. }
  27427. // Ignore in case of colorAxis or zAxis. #3360, #3524, #6720
  27428. if (axis.coll !== 'xAxis' && axis.coll !== 'yAxis') {
  27429. return proceed.apply(this, [].slice.call(arguments, 1));
  27430. }
  27431. // Get the related axes based on series
  27432. axes = getAxis(axis.coll);
  27433. // Get the related axes based options.*Axis setting #2810
  27434. axes2 = (axis.isXAxis ? chart.yAxis : chart.xAxis);
  27435. each(axes2, function(A) {
  27436. if (defined(A.options.id) ? A.options.id.indexOf('navigator') === -1 : true) {
  27437. var a = (A.isXAxis ? 'yAxis' : 'xAxis'),
  27438. rax = (defined(A.options[a]) ? chart[a][A.options[a]] : chart[a][0]);
  27439. if (axis === rax) {
  27440. axes.push(A);
  27441. }
  27442. }
  27443. });
  27444. // Remove duplicates in the axes array. If there are no axes in the axes array,
  27445. // we are adding an axis without data, so we need to populate this with grid
  27446. // lines (#2796).
  27447. uniqueAxes = axes.length ? [] : [axis.isXAxis ? chart.yAxis[0] : chart.xAxis[0]]; //#3742
  27448. each(axes, function(axis2) {
  27449. if (
  27450. inArray(axis2, uniqueAxes) === -1 &&
  27451. // Do not draw on axis which overlap completely. #5424
  27452. !H.find(uniqueAxes, function(unique) {
  27453. return unique.pos === axis2.pos && unique.len && axis2.len;
  27454. })
  27455. ) {
  27456. uniqueAxes.push(axis2);
  27457. }
  27458. });
  27459. transVal = pick(translatedValue, axis.translate(value, null, null, old));
  27460. if (isNumber(transVal)) {
  27461. if (axis.horiz) {
  27462. each(uniqueAxes, function(axis2) {
  27463. var skip;
  27464. y1 = axis2.pos;
  27465. y2 = y1 + axis2.len;
  27466. x1 = x2 = Math.round(transVal + axis.transB);
  27467. if (x1 < axisLeft || x1 > axisLeft + axis.width) { // outside plot area
  27468. if (force) {
  27469. x1 = x2 = Math.min(Math.max(axisLeft, x1), axisLeft + axis.width);
  27470. } else {
  27471. skip = true;
  27472. }
  27473. }
  27474. if (!skip) {
  27475. result.push('M', x1, y1, 'L', x2, y2);
  27476. }
  27477. });
  27478. } else {
  27479. each(uniqueAxes, function(axis2) {
  27480. var skip;
  27481. x1 = axis2.pos;
  27482. x2 = x1 + axis2.len;
  27483. y1 = y2 = Math.round(axisTop + axis.height - transVal);
  27484. if (y1 < axisTop || y1 > axisTop + axis.height) { // outside plot area
  27485. if (force) {
  27486. y1 = y2 = Math.min(Math.max(axisTop, y1), axis.top + axis.height);
  27487. } else {
  27488. skip = true;
  27489. }
  27490. }
  27491. if (!skip) {
  27492. result.push('M', x1, y1, 'L', x2, y2);
  27493. }
  27494. });
  27495. }
  27496. }
  27497. return result.length > 0 ?
  27498. renderer.crispPolyLine(result, lineWidth || 1) :
  27499. null; //#3557 getPlotLinePath in regular Highcharts also returns null
  27500. });
  27501. // Override getPlotBandPath to allow for multipane charts
  27502. Axis.prototype.getPlotBandPath = function(from, to) {
  27503. var toPath = this.getPlotLinePath(to, null, null, true),
  27504. path = this.getPlotLinePath(from, null, null, true),
  27505. result = [],
  27506. i;
  27507. if (path && toPath) {
  27508. if (path.toString() === toPath.toString()) {
  27509. // #6166
  27510. result = path;
  27511. result.flat = true;
  27512. } else {
  27513. // Go over each subpath
  27514. for (i = 0; i < path.length; i += 6) {
  27515. result.push(
  27516. 'M', path[i + 1], path[i + 2],
  27517. 'L', path[i + 4], path[i + 5],
  27518. toPath[i + 4], toPath[i + 5],
  27519. toPath[i + 1], toPath[i + 2],
  27520. 'z'
  27521. );
  27522. }
  27523. }
  27524. } else { // outside the axis area
  27525. result = null;
  27526. }
  27527. return result;
  27528. };
  27529. // Function to crisp a line with multiple segments
  27530. SVGRenderer.prototype.crispPolyLine = function(points, width) {
  27531. // points format: ['M', 0, 0, 'L', 100, 0]
  27532. // normalize to a crisp line
  27533. var i;
  27534. for (i = 0; i < points.length; i = i + 6) {
  27535. if (points[i + 1] === points[i + 4]) {
  27536. // Substract due to #1129. Now bottom and left axis gridlines behave the same.
  27537. points[i + 1] = points[i + 4] = Math.round(points[i + 1]) - (width % 2 / 2);
  27538. }
  27539. if (points[i + 2] === points[i + 5]) {
  27540. points[i + 2] = points[i + 5] = Math.round(points[i + 2]) + (width % 2 / 2);
  27541. }
  27542. }
  27543. return points;
  27544. };
  27545. if (Renderer === VMLRenderer) {
  27546. VMLRenderer.prototype.crispPolyLine = SVGRenderer.prototype.crispPolyLine;
  27547. }
  27548. // Wrapper to hide the label
  27549. wrap(Axis.prototype, 'hideCrosshair', function(proceed, i) {
  27550. proceed.call(this, i);
  27551. if (this.crossLabel) {
  27552. this.crossLabel = this.crossLabel.hide();
  27553. }
  27554. });
  27555. // Wrapper to draw the label
  27556. wrap(Axis.prototype, 'drawCrosshair', function(proceed, e, point) {
  27557. // Draw the crosshair
  27558. proceed.call(this, e, point);
  27559. // Check if the label has to be drawn
  27560. if (!defined(this.crosshair.label) || !this.crosshair.label.enabled || !this.cross) {
  27561. return;
  27562. }
  27563. var chart = this.chart,
  27564. options = this.options.crosshair.label, // the label's options
  27565. horiz = this.horiz, // axis orientation
  27566. opposite = this.opposite, // axis position
  27567. left = this.left, // left position
  27568. top = this.top, // top position
  27569. crossLabel = this.crossLabel, // reference to the svgElement
  27570. posx,
  27571. posy,
  27572. crossBox,
  27573. formatOption = options.format,
  27574. formatFormat = '',
  27575. limit,
  27576. align,
  27577. tickInside = this.options.tickPosition === 'inside',
  27578. snap = this.crosshair.snap !== false,
  27579. value,
  27580. offset = 0;
  27581. // Use last available event (#5287)
  27582. if (!e) {
  27583. e = this.cross && this.cross.e;
  27584. }
  27585. align = (horiz ? 'center' : opposite ?
  27586. (this.labelAlign === 'right' ? 'right' : 'left') :
  27587. (this.labelAlign === 'left' ? 'left' : 'center'));
  27588. // If the label does not exist yet, create it.
  27589. if (!crossLabel) {
  27590. crossLabel = this.crossLabel = chart.renderer.label(null, null, null, options.shape || 'callout')
  27591. .addClass('highcharts-crosshair-label' +
  27592. (this.series[0] && ' highcharts-color-' + this.series[0].colorIndex))
  27593. .attr({
  27594. align: options.align || align,
  27595. padding: pick(options.padding, 8),
  27596. r: pick(options.borderRadius, 3),
  27597. zIndex: 2
  27598. })
  27599. .add(this.labelGroup);
  27600. // Presentational
  27601. crossLabel
  27602. .attr({
  27603. fill: options.backgroundColor ||
  27604. (this.series[0] && this.series[0].color) || '#666666',
  27605. stroke: options.borderColor || '',
  27606. 'stroke-width': options.borderWidth || 0
  27607. })
  27608. .css(extend({
  27609. color: '#ffffff',
  27610. fontWeight: 'normal',
  27611. fontSize: '11px',
  27612. textAlign: 'center'
  27613. }, options.style));
  27614. }
  27615. if (horiz) {
  27616. posx = snap ? point.plotX + left : e.chartX;
  27617. posy = top + (opposite ? 0 : this.height);
  27618. } else {
  27619. posx = opposite ? this.width + left : 0;
  27620. posy = snap ? point.plotY + top : e.chartY;
  27621. }
  27622. if (!formatOption && !options.formatter) {
  27623. if (this.isDatetimeAxis) {
  27624. formatFormat = '%b %d, %Y';
  27625. }
  27626. formatOption = '{value' + (formatFormat ? ':' + formatFormat : '') + '}';
  27627. }
  27628. // Show the label
  27629. value = snap ? point[this.isXAxis ? 'x' : 'y'] : this.toValue(horiz ? e.chartX : e.chartY);
  27630. crossLabel.attr({
  27631. text: formatOption ? format(formatOption, {
  27632. value: value
  27633. }) : options.formatter.call(this, value),
  27634. x: posx,
  27635. y: posy,
  27636. visibility: 'visible'
  27637. });
  27638. crossBox = crossLabel.getBBox();
  27639. // now it is placed we can correct its position
  27640. if (horiz) {
  27641. if ((tickInside && !opposite) || (!tickInside && opposite)) {
  27642. posy = crossLabel.y - crossBox.height;
  27643. }
  27644. } else {
  27645. posy = crossLabel.y - (crossBox.height / 2);
  27646. }
  27647. // check the edges
  27648. if (horiz) {
  27649. limit = {
  27650. left: left - crossBox.x,
  27651. right: left + this.width - crossBox.x
  27652. };
  27653. } else {
  27654. limit = {
  27655. left: this.labelAlign === 'left' ? left : 0,
  27656. right: this.labelAlign === 'right' ? left + this.width : chart.chartWidth
  27657. };
  27658. }
  27659. // left edge
  27660. if (crossLabel.translateX < limit.left) {
  27661. offset = limit.left - crossLabel.translateX;
  27662. }
  27663. // right edge
  27664. if (crossLabel.translateX + crossBox.width >= limit.right) {
  27665. offset = -(crossLabel.translateX + crossBox.width - limit.right);
  27666. }
  27667. // show the crosslabel
  27668. crossLabel.attr({
  27669. x: posx + offset,
  27670. y: posy,
  27671. // First set x and y, then anchorX and anchorY, when box is actually calculated, #5702
  27672. anchorX: horiz ? posx : (this.opposite ? 0 : chart.chartWidth),
  27673. anchorY: horiz ? (this.opposite ? chart.chartHeight : 0) : posy + crossBox.height / 2
  27674. });
  27675. });
  27676. /* ****************************************************************************
  27677. * Start value compare logic *
  27678. *****************************************************************************/
  27679. /**
  27680. * Extend series.init by adding a method to modify the y value used for plotting
  27681. * on the y axis. This method is called both from the axis when finding dataMin
  27682. * and dataMax, and from the series.translate method.
  27683. */
  27684. seriesProto.init = function() {
  27685. // Call base method
  27686. seriesInit.apply(this, arguments);
  27687. // Set comparison mode
  27688. this.setCompare(this.options.compare);
  27689. };
  27690. /**
  27691. * Highstock only. Set the {@link
  27692. * http://api.highcharts.com/highstock/plotOptions.series.compare|
  27693. * compare} mode of the series after render time. In most cases it is more
  27694. * useful running {@link Axis#setCompare} on the X axis to update all its
  27695. * series.
  27696. *
  27697. * @function setCompare
  27698. * @memberOf Series.prototype
  27699. *
  27700. * @param {String} compare
  27701. * Can be one of `null`, `"percent"` or `"value"`.
  27702. */
  27703. seriesProto.setCompare = function(compare) {
  27704. // Set or unset the modifyValue method
  27705. this.modifyValue = (compare === 'value' || compare === 'percent') ? function(value, point) {
  27706. var compareValue = this.compareValue;
  27707. if (value !== undefined && compareValue !== undefined) { // #2601, #5814
  27708. // Get the modified value
  27709. if (compare === 'value') {
  27710. value -= compareValue;
  27711. // Compare percent
  27712. } else {
  27713. value = 100 * (value / compareValue) -
  27714. (this.options.compareBase === 100 ? 0 : 100);
  27715. }
  27716. // record for tooltip etc.
  27717. if (point) {
  27718. point.change = value;
  27719. }
  27720. return value;
  27721. }
  27722. } : null;
  27723. // Survive to export, #5485
  27724. this.userOptions.compare = compare;
  27725. // Mark dirty
  27726. if (this.chart.hasRendered) {
  27727. this.isDirty = true;
  27728. }
  27729. };
  27730. /**
  27731. * Extend series.processData by finding the first y value in the plot area,
  27732. * used for comparing the following values
  27733. */
  27734. seriesProto.processData = function() {
  27735. var series = this,
  27736. i,
  27737. keyIndex = -1,
  27738. processedXData,
  27739. processedYData,
  27740. length,
  27741. compareValue;
  27742. // call base method
  27743. seriesProcessData.apply(this, arguments);
  27744. if (series.xAxis && series.processedYData) { // not pies
  27745. // local variables
  27746. processedXData = series.processedXData;
  27747. processedYData = series.processedYData;
  27748. length = processedYData.length;
  27749. // For series with more than one value (range, OHLC etc), compare against
  27750. // close or the pointValKey (#4922, #3112)
  27751. if (series.pointArrayMap) {
  27752. // Use close if present (#3112)
  27753. keyIndex = inArray('close', series.pointArrayMap);
  27754. if (keyIndex === -1) {
  27755. keyIndex = inArray(series.pointValKey || 'y', series.pointArrayMap);
  27756. }
  27757. }
  27758. // find the first value for comparison
  27759. for (i = 0; i < length - 1; i++) {
  27760. compareValue = processedYData[i] && keyIndex > -1 ?
  27761. processedYData[i][keyIndex] :
  27762. processedYData[i];
  27763. if (isNumber(compareValue) && processedXData[i + 1] >= series.xAxis.min && compareValue !== 0) {
  27764. series.compareValue = compareValue;
  27765. break;
  27766. }
  27767. }
  27768. }
  27769. };
  27770. /**
  27771. * Modify series extremes
  27772. */
  27773. wrap(seriesProto, 'getExtremes', function(proceed) {
  27774. var extremes;
  27775. proceed.apply(this, [].slice.call(arguments, 1));
  27776. if (this.modifyValue) {
  27777. extremes = [this.modifyValue(this.dataMin), this.modifyValue(this.dataMax)];
  27778. this.dataMin = arrayMin(extremes);
  27779. this.dataMax = arrayMax(extremes);
  27780. }
  27781. });
  27782. /**
  27783. * Highstock only. Set the compare mode on all series belonging to an Y axis
  27784. * after render time.
  27785. *
  27786. * @param {String} compare
  27787. * The compare mode. Can be one of `null`, `"value"` or `"percent"`.
  27788. * @param {Boolean} [redraw=true]
  27789. * Whether to redraw the chart or to wait for a later call to {@link
  27790. * Chart#redraw},
  27791. *
  27792. * @function setCompare
  27793. * @memberOf Axis.prototype
  27794. *
  27795. * @see {@link https://api.highcharts.com/highstock/series.plotOptions.compare|
  27796. * series.plotOptions.compare}
  27797. *
  27798. * @sample stock/members/axis-setcompare/
  27799. * Set compoare
  27800. */
  27801. Axis.prototype.setCompare = function(compare, redraw) {
  27802. if (!this.isXAxis) {
  27803. each(this.series, function(series) {
  27804. series.setCompare(compare);
  27805. });
  27806. if (pick(redraw, true)) {
  27807. this.chart.redraw();
  27808. }
  27809. }
  27810. };
  27811. /**
  27812. * Extend the tooltip formatter by adding support for the point.change variable
  27813. * as well as the changeDecimals option
  27814. */
  27815. Point.prototype.tooltipFormatter = function(pointFormat) {
  27816. var point = this;
  27817. pointFormat = pointFormat.replace(
  27818. '{point.change}',
  27819. (point.change > 0 ? '+' : '') +
  27820. H.numberFormat(point.change, pick(point.series.tooltipOptions.changeDecimals, 2))
  27821. );
  27822. return pointTooltipFormatter.apply(this, [pointFormat]);
  27823. };
  27824. /* ****************************************************************************
  27825. * End value compare logic *
  27826. *****************************************************************************/
  27827. /**
  27828. * Extend the Series prototype to create a separate series clip box. This is
  27829. * related to using multiple panes, and a future pane logic should incorporate
  27830. * this feature (#2754).
  27831. */
  27832. wrap(Series.prototype, 'render', function(proceed) {
  27833. // Only do this on not 3d (#2939, #5904) nor polar (#6057) charts, and only
  27834. // if the series type handles clipping in the animate method (#2975).
  27835. if (!(this.chart.is3d && this.chart.is3d()) &&
  27836. !this.chart.polar &&
  27837. this.xAxis &&
  27838. !this.xAxis.isRadial // Gauge, #6192
  27839. ) {
  27840. // First render, initial clip box
  27841. if (!this.clipBox && this.animate) {
  27842. this.clipBox = merge(this.chart.clipBox);
  27843. this.clipBox.width = this.xAxis.len;
  27844. this.clipBox.height = this.yAxis.len;
  27845. // On redrawing, resizing etc, update the clip rectangle
  27846. } else if (this.chart[this.sharedClipKey]) {
  27847. this.chart[this.sharedClipKey].attr({
  27848. width: this.xAxis.len,
  27849. height: this.yAxis.len
  27850. });
  27851. // #3111
  27852. } else if (this.clipBox) {
  27853. this.clipBox.width = this.xAxis.len;
  27854. this.clipBox.height = this.yAxis.len;
  27855. }
  27856. }
  27857. proceed.call(this);
  27858. });
  27859. wrap(Chart.prototype, 'getSelectedPoints', function(proceed) {
  27860. var points = proceed.call(this);
  27861. each(this.series, function(serie) {
  27862. // series.points - for grouped points (#6445)
  27863. if (serie.hasGroupedData) {
  27864. points = points.concat(grep(serie.points || [], function(point) {
  27865. return point.selected;
  27866. }));
  27867. }
  27868. });
  27869. return points;
  27870. });
  27871. wrap(Chart.prototype, 'update', function(proceed, options) {
  27872. // Use case: enabling scrollbar from a disabled state.
  27873. // Scrollbar needs to be initialized from a controller, Navigator in this
  27874. // case (#6615)
  27875. if ('scrollbar' in options && this.navigator) {
  27876. merge(true, this.options.scrollbar, options.scrollbar);
  27877. this.navigator.update({}, false);
  27878. delete options.scrollbar;
  27879. }
  27880. return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  27881. });
  27882. }(Highcharts));
  27883. (function() {
  27884. }());
  27885. return Highcharts
  27886. }));