stock.src.js 439 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900
  1. /**
  2. * @license Highstock JS v8.2.0 (2020-08-20)
  3. *
  4. * Highstock as a plugin for Highcharts
  5. *
  6. * (c) 2010-2019 Torstein Honsi
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. factory['default'] = factory;
  14. module.exports = factory;
  15. } else if (typeof define === 'function' && define.amd) {
  16. define('highcharts/modules/stock', ['highcharts'], function (Highcharts) {
  17. factory(Highcharts);
  18. factory.Highcharts = Highcharts;
  19. return factory;
  20. });
  21. } else {
  22. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  23. }
  24. }(function (Highcharts) {
  25. var _modules = Highcharts ? Highcharts._modules : {};
  26. function _registerModule(obj, path, args, fn) {
  27. if (!obj.hasOwnProperty(path)) {
  28. obj[path] = fn.apply(null, args);
  29. }
  30. }
  31. _registerModule(_modules, 'Core/Axis/NavigatorAxis.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) {
  32. /* *
  33. *
  34. * (c) 2010-2020 Torstein Honsi
  35. *
  36. * License: www.highcharts.com/license
  37. *
  38. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  39. *
  40. * */
  41. var isTouchDevice = H.isTouchDevice;
  42. var addEvent = U.addEvent,
  43. correctFloat = U.correctFloat,
  44. defined = U.defined,
  45. isNumber = U.isNumber,
  46. pick = U.pick;
  47. /* eslint-disable valid-jsdoc */
  48. /**
  49. * @private
  50. * @class
  51. */
  52. var NavigatorAxisAdditions = /** @class */ (function () {
  53. /* *
  54. *
  55. * Constructors
  56. *
  57. * */
  58. function NavigatorAxisAdditions(axis) {
  59. this.axis = axis;
  60. }
  61. /* *
  62. *
  63. * Functions
  64. *
  65. * */
  66. /**
  67. * @private
  68. */
  69. NavigatorAxisAdditions.prototype.destroy = function () {
  70. this.axis = void 0;
  71. };
  72. /**
  73. * Add logic to normalize the zoomed range in order to preserve the pressed
  74. * state of range selector buttons
  75. *
  76. * @private
  77. * @function Highcharts.Axis#toFixedRange
  78. * @param {number} [pxMin]
  79. * @param {number} [pxMax]
  80. * @param {number} [fixedMin]
  81. * @param {number} [fixedMax]
  82. * @return {*}
  83. */
  84. NavigatorAxisAdditions.prototype.toFixedRange = function (pxMin, pxMax, fixedMin, fixedMax) {
  85. var navigator = this;
  86. var axis = navigator.axis;
  87. var chart = axis.chart;
  88. var fixedRange = chart && chart.fixedRange,
  89. halfPointRange = (axis.pointRange || 0) / 2,
  90. newMin = pick(fixedMin,
  91. axis.translate(pxMin,
  92. true, !axis.horiz)),
  93. newMax = pick(fixedMax,
  94. axis.translate(pxMax,
  95. true, !axis.horiz)),
  96. changeRatio = fixedRange && (newMax - newMin) / fixedRange;
  97. // Add/remove half point range to/from the extremes (#1172)
  98. if (!defined(fixedMin)) {
  99. newMin = correctFloat(newMin + halfPointRange);
  100. }
  101. if (!defined(fixedMax)) {
  102. newMax = correctFloat(newMax - halfPointRange);
  103. }
  104. // If the difference between the fixed range and the actual requested
  105. // range is too great, the user is dragging across an ordinal gap, and
  106. // we need to release the range selector button.
  107. if (changeRatio > 0.7 && changeRatio < 1.3) {
  108. if (fixedMax) {
  109. newMin = newMax - fixedRange;
  110. }
  111. else {
  112. newMax = newMin + fixedRange;
  113. }
  114. }
  115. if (!isNumber(newMin) || !isNumber(newMax)) { // #1195, #7411
  116. newMin = newMax = void 0;
  117. }
  118. return {
  119. min: newMin,
  120. max: newMax
  121. };
  122. };
  123. return NavigatorAxisAdditions;
  124. }());
  125. /**
  126. * @private
  127. * @class
  128. */
  129. var NavigatorAxis = /** @class */ (function () {
  130. function NavigatorAxis() {
  131. }
  132. /* *
  133. *
  134. * Static Functions
  135. *
  136. * */
  137. /**
  138. * @private
  139. */
  140. NavigatorAxis.compose = function (AxisClass) {
  141. AxisClass.keepProps.push('navigatorAxis');
  142. /* eslint-disable no-invalid-this */
  143. addEvent(AxisClass, 'init', function () {
  144. var axis = this;
  145. if (!axis.navigatorAxis) {
  146. axis.navigatorAxis = new NavigatorAxisAdditions(axis);
  147. }
  148. });
  149. // For Stock charts, override selection zooming with some special
  150. // features because X axis zooming is already allowed by the Navigator
  151. // and Range selector.
  152. addEvent(AxisClass, 'zoom', function (e) {
  153. var axis = this;
  154. var chart = axis.chart;
  155. var chartOptions = chart.options;
  156. var navigator = chartOptions.navigator;
  157. var navigatorAxis = axis.navigatorAxis;
  158. var pinchType = chartOptions.chart.pinchType;
  159. var rangeSelector = chartOptions.rangeSelector;
  160. var zoomType = chartOptions.chart.zoomType;
  161. var previousZoom;
  162. if (axis.isXAxis && ((navigator && navigator.enabled) ||
  163. (rangeSelector && rangeSelector.enabled))) {
  164. // For y only zooming, ignore the X axis completely
  165. if (zoomType === 'y') {
  166. e.zoomed = false;
  167. // For xy zooming, record the state of the zoom before zoom
  168. // selection, then when the reset button is pressed, revert to
  169. // this state. This should apply only if the chart is
  170. // initialized with a range (#6612), otherwise zoom all the way
  171. // out.
  172. }
  173. else if (((!isTouchDevice && zoomType === 'xy') ||
  174. (isTouchDevice && pinchType === 'xy')) &&
  175. axis.options.range) {
  176. previousZoom = navigatorAxis.previousZoom;
  177. if (defined(e.newMin)) {
  178. navigatorAxis.previousZoom = [axis.min, axis.max];
  179. }
  180. else if (previousZoom) {
  181. e.newMin = previousZoom[0];
  182. e.newMax = previousZoom[1];
  183. navigatorAxis.previousZoom = void 0;
  184. }
  185. }
  186. }
  187. if (typeof e.zoomed !== 'undefined') {
  188. e.preventDefault();
  189. }
  190. });
  191. /* eslint-enable no-invalid-this */
  192. };
  193. /* *
  194. *
  195. * Static Properties
  196. *
  197. * */
  198. /**
  199. * @private
  200. */
  201. NavigatorAxis.AdditionsClass = NavigatorAxisAdditions;
  202. return NavigatorAxis;
  203. }());
  204. return NavigatorAxis;
  205. });
  206. _registerModule(_modules, 'Core/Axis/ScrollbarAxis.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) {
  207. /* *
  208. *
  209. * (c) 2010-2020 Torstein Honsi
  210. *
  211. * License: www.highcharts.com/license
  212. *
  213. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  214. *
  215. * */
  216. var addEvent = U.addEvent,
  217. defined = U.defined,
  218. pick = U.pick;
  219. /* eslint-disable no-invalid-this, valid-jsdoc */
  220. /**
  221. * Creates scrollbars if enabled.
  222. *
  223. * @private
  224. */
  225. var ScrollbarAxis = /** @class */ (function () {
  226. function ScrollbarAxis() {
  227. }
  228. /**
  229. * Attaches to axis events to create scrollbars if enabled.
  230. *
  231. * @private
  232. *
  233. * @param AxisClass
  234. * Axis class to extend.
  235. *
  236. * @param ScrollbarClass
  237. * Scrollbar class to use.
  238. */
  239. ScrollbarAxis.compose = function (AxisClass, ScrollbarClass) {
  240. // Wrap axis initialization and create scrollbar if enabled:
  241. addEvent(AxisClass, 'afterInit', function () {
  242. var axis = this;
  243. if (axis.options &&
  244. axis.options.scrollbar &&
  245. axis.options.scrollbar.enabled) {
  246. // Predefined options:
  247. axis.options.scrollbar.vertical = !axis.horiz;
  248. axis.options.startOnTick = axis.options.endOnTick = false;
  249. axis.scrollbar = new ScrollbarClass(axis.chart.renderer, axis.options.scrollbar, axis.chart);
  250. addEvent(axis.scrollbar, 'changed', function (e) {
  251. var axisMin = pick(axis.options && axis.options.min,
  252. axis.min),
  253. axisMax = pick(axis.options && axis.options.max,
  254. axis.max),
  255. unitedMin = defined(axis.dataMin) ?
  256. Math.min(axisMin,
  257. axis.min,
  258. axis.dataMin) : axisMin,
  259. unitedMax = defined(axis.dataMax) ?
  260. Math.max(axisMax,
  261. axis.max,
  262. axis.dataMax) : axisMax,
  263. range = unitedMax - unitedMin,
  264. to,
  265. from;
  266. // #12834, scroll when show/hide series, wrong extremes
  267. if (!defined(axisMin) || !defined(axisMax)) {
  268. return;
  269. }
  270. if ((axis.horiz && !axis.reversed) ||
  271. (!axis.horiz && axis.reversed)) {
  272. to = unitedMin + range * this.to;
  273. from = unitedMin + range * this.from;
  274. }
  275. else {
  276. // y-values in browser are reversed, but this also
  277. // applies for reversed horizontal axis:
  278. to = unitedMin + range * (1 - this.from);
  279. from = unitedMin + range * (1 - this.to);
  280. }
  281. if (pick(this.options.liveRedraw, H.svg && !H.isTouchDevice && !this.chart.isBoosting) ||
  282. // Mouseup always should change extremes
  283. e.DOMType === 'mouseup' ||
  284. // Internal events
  285. !defined(e.DOMType)) {
  286. axis.setExtremes(from, to, true, e.DOMType !== 'mousemove', e);
  287. }
  288. else {
  289. // When live redraw is disabled, don't change extremes
  290. // Only change the position of the scollbar thumb
  291. this.setRange(this.from, this.to);
  292. }
  293. });
  294. }
  295. });
  296. // Wrap rendering axis, and update scrollbar if one is created:
  297. addEvent(AxisClass, 'afterRender', function () {
  298. var axis = this,
  299. scrollMin = Math.min(pick(axis.options.min,
  300. axis.min),
  301. axis.min,
  302. pick(axis.dataMin,
  303. axis.min) // #6930
  304. ),
  305. scrollMax = Math.max(pick(axis.options.max,
  306. axis.max),
  307. axis.max,
  308. pick(axis.dataMax,
  309. axis.max) // #6930
  310. ),
  311. scrollbar = axis.scrollbar,
  312. offset = axis.axisTitleMargin + (axis.titleOffset || 0),
  313. scrollbarsOffsets = axis.chart.scrollbarsOffsets,
  314. axisMargin = axis.options.margin || 0,
  315. offsetsIndex,
  316. from,
  317. to;
  318. if (scrollbar) {
  319. if (axis.horiz) {
  320. // Reserve space for labels/title
  321. if (!axis.opposite) {
  322. scrollbarsOffsets[1] += offset;
  323. }
  324. scrollbar.position(axis.left, axis.top + axis.height + 2 + scrollbarsOffsets[1] -
  325. (axis.opposite ? axisMargin : 0), axis.width, axis.height);
  326. // Next scrollbar should reserve space for margin (if set)
  327. if (!axis.opposite) {
  328. scrollbarsOffsets[1] += axisMargin;
  329. }
  330. offsetsIndex = 1;
  331. }
  332. else {
  333. // Reserve space for labels/title
  334. if (axis.opposite) {
  335. scrollbarsOffsets[0] += offset;
  336. }
  337. scrollbar.position(axis.left + axis.width + 2 + scrollbarsOffsets[0] -
  338. (axis.opposite ? 0 : axisMargin), axis.top, axis.width, axis.height);
  339. // Next scrollbar should reserve space for margin (if set)
  340. if (axis.opposite) {
  341. scrollbarsOffsets[0] += axisMargin;
  342. }
  343. offsetsIndex = 0;
  344. }
  345. scrollbarsOffsets[offsetsIndex] += scrollbar.size +
  346. scrollbar.options.margin;
  347. if (isNaN(scrollMin) ||
  348. isNaN(scrollMax) ||
  349. !defined(axis.min) ||
  350. !defined(axis.max) ||
  351. axis.min === axis.max // #10733
  352. ) {
  353. // default action: when extremes are the same or there is
  354. // not extremes on the axis, but scrollbar exists, make it
  355. // full size
  356. scrollbar.setRange(0, 1);
  357. }
  358. else {
  359. from =
  360. (axis.min - scrollMin) / (scrollMax - scrollMin);
  361. to =
  362. (axis.max - scrollMin) / (scrollMax - scrollMin);
  363. if ((axis.horiz && !axis.reversed) ||
  364. (!axis.horiz && axis.reversed)) {
  365. scrollbar.setRange(from, to);
  366. }
  367. else {
  368. // inverse vertical axis
  369. scrollbar.setRange(1 - to, 1 - from);
  370. }
  371. }
  372. }
  373. });
  374. // Make space for a scrollbar:
  375. addEvent(AxisClass, 'afterGetOffset', function () {
  376. var axis = this,
  377. index = axis.horiz ? 2 : 1,
  378. scrollbar = axis.scrollbar;
  379. if (scrollbar) {
  380. axis.chart.scrollbarsOffsets = [0, 0]; // reset scrollbars offsets
  381. axis.chart.axisOffset[index] +=
  382. scrollbar.size + scrollbar.options.margin;
  383. }
  384. });
  385. };
  386. return ScrollbarAxis;
  387. }());
  388. return ScrollbarAxis;
  389. });
  390. _registerModule(_modules, 'Core/Scrollbar.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Globals.js'], _modules['Core/Axis/ScrollbarAxis.js'], _modules['Core/Utilities.js'], _modules['Core/Options.js']], function (Axis, H, ScrollbarAxis, U, O) {
  391. /* *
  392. *
  393. * (c) 2010-2020 Torstein Honsi
  394. *
  395. * License: www.highcharts.com/license
  396. *
  397. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  398. *
  399. * */
  400. var addEvent = U.addEvent,
  401. correctFloat = U.correctFloat,
  402. defined = U.defined,
  403. destroyObjectProperties = U.destroyObjectProperties,
  404. fireEvent = U.fireEvent,
  405. merge = U.merge,
  406. pick = U.pick,
  407. removeEvent = U.removeEvent;
  408. var defaultOptions = O.defaultOptions;
  409. var hasTouch = H.hasTouch,
  410. isTouchDevice = H.isTouchDevice;
  411. /**
  412. * When we have vertical scrollbar, rifles and arrow in buttons should be
  413. * rotated. The same method is used in Navigator's handles, to rotate them.
  414. *
  415. * @function Highcharts.swapXY
  416. *
  417. * @param {Highcharts.SVGPathArray} path
  418. * Path to be rotated.
  419. *
  420. * @param {boolean} [vertical]
  421. * If vertical scrollbar, swap x-y values.
  422. *
  423. * @return {Highcharts.SVGPathArray}
  424. * Rotated path.
  425. *
  426. * @requires modules/stock
  427. */
  428. var swapXY = H.swapXY = function (path,
  429. vertical) {
  430. if (vertical) {
  431. path.forEach(function (seg) {
  432. var len = seg.length;
  433. var temp;
  434. for (var i = 0; i < len; i += 2) {
  435. temp = seg[i + 1];
  436. if (typeof temp === 'number') {
  437. seg[i + 1] = seg[i + 2];
  438. seg[i + 2] = temp;
  439. }
  440. }
  441. });
  442. }
  443. return path;
  444. };
  445. /* eslint-disable no-invalid-this, valid-jsdoc */
  446. /**
  447. * A reusable scrollbar, internally used in Highstock's navigator and optionally
  448. * on individual axes.
  449. *
  450. * @private
  451. * @class
  452. * @name Highcharts.Scrollbar
  453. * @param {Highcharts.SVGRenderer} renderer
  454. * @param {Highcharts.ScrollbarOptions} options
  455. * @param {Highcharts.Chart} chart
  456. */
  457. var Scrollbar = /** @class */ (function () {
  458. /* *
  459. *
  460. * Constructors
  461. *
  462. * */
  463. function Scrollbar(renderer, options, chart) {
  464. /* *
  465. *
  466. * Properties
  467. *
  468. * */
  469. this._events = [];
  470. this.chartX = 0;
  471. this.chartY = 0;
  472. this.from = 0;
  473. this.group = void 0;
  474. this.scrollbar = void 0;
  475. this.scrollbarButtons = [];
  476. this.scrollbarGroup = void 0;
  477. this.scrollbarLeft = 0;
  478. this.scrollbarRifles = void 0;
  479. this.scrollbarStrokeWidth = 1;
  480. this.scrollbarTop = 0;
  481. this.size = 0;
  482. this.to = 0;
  483. this.track = void 0;
  484. this.trackBorderWidth = 1;
  485. this.userOptions = {};
  486. this.x = 0;
  487. this.y = 0;
  488. this.chart = chart;
  489. this.options = options;
  490. this.renderer = chart.renderer;
  491. this.init(renderer, options, chart);
  492. }
  493. /* *
  494. *
  495. * Functions
  496. *
  497. * */
  498. /**
  499. * Set up the mouse and touch events for the Scrollbar
  500. *
  501. * @private
  502. * @function Highcharts.Scrollbar#addEvents
  503. * @return {void}
  504. */
  505. Scrollbar.prototype.addEvents = function () {
  506. var buttonsOrder = this.options.inverted ? [1, 0] : [0, 1],
  507. buttons = this.scrollbarButtons,
  508. bar = this.scrollbarGroup.element,
  509. track = this.track.element,
  510. mouseDownHandler = this.mouseDownHandler.bind(this),
  511. mouseMoveHandler = this.mouseMoveHandler.bind(this),
  512. mouseUpHandler = this.mouseUpHandler.bind(this),
  513. _events;
  514. // Mouse events
  515. _events = [
  516. [buttons[buttonsOrder[0]].element, 'click', this.buttonToMinClick.bind(this)],
  517. [buttons[buttonsOrder[1]].element, 'click', this.buttonToMaxClick.bind(this)],
  518. [track, 'click', this.trackClick.bind(this)],
  519. [bar, 'mousedown', mouseDownHandler],
  520. [bar.ownerDocument, 'mousemove', mouseMoveHandler],
  521. [bar.ownerDocument, 'mouseup', mouseUpHandler]
  522. ];
  523. // Touch events
  524. if (hasTouch) {
  525. _events.push([bar, 'touchstart', mouseDownHandler], [bar.ownerDocument, 'touchmove', mouseMoveHandler], [bar.ownerDocument, 'touchend', mouseUpHandler]);
  526. }
  527. // Add them all
  528. _events.forEach(function (args) {
  529. addEvent.apply(null, args);
  530. });
  531. this._events = _events;
  532. };
  533. Scrollbar.prototype.buttonToMaxClick = function (e) {
  534. var scroller = this;
  535. var range = (scroller.to - scroller.from) * pick(scroller.options.step, 0.2);
  536. scroller.updatePosition(scroller.from + range, scroller.to + range);
  537. fireEvent(scroller, 'changed', {
  538. from: scroller.from,
  539. to: scroller.to,
  540. trigger: 'scrollbar',
  541. DOMEvent: e
  542. });
  543. };
  544. Scrollbar.prototype.buttonToMinClick = function (e) {
  545. var scroller = this;
  546. var range = correctFloat(scroller.to - scroller.from) *
  547. pick(scroller.options.step, 0.2);
  548. scroller.updatePosition(correctFloat(scroller.from - range), correctFloat(scroller.to - range));
  549. fireEvent(scroller, 'changed', {
  550. from: scroller.from,
  551. to: scroller.to,
  552. trigger: 'scrollbar',
  553. DOMEvent: e
  554. });
  555. };
  556. /**
  557. * Get normalized (0-1) cursor position over the scrollbar
  558. *
  559. * @private
  560. * @function Highcharts.Scrollbar#cursorToScrollbarPosition
  561. *
  562. * @param {*} normalizedEvent
  563. * normalized event, with chartX and chartY values
  564. *
  565. * @return {Highcharts.Dictionary<number>}
  566. * Local position {chartX, chartY}
  567. */
  568. Scrollbar.prototype.cursorToScrollbarPosition = function (normalizedEvent) {
  569. var scroller = this,
  570. options = scroller.options,
  571. minWidthDifference = options.minWidth > scroller.calculatedWidth ?
  572. options.minWidth :
  573. 0; // minWidth distorts translation
  574. return {
  575. chartX: (normalizedEvent.chartX - scroller.x -
  576. scroller.xOffset) /
  577. (scroller.barWidth - minWidthDifference),
  578. chartY: (normalizedEvent.chartY - scroller.y -
  579. scroller.yOffset) /
  580. (scroller.barWidth - minWidthDifference)
  581. };
  582. };
  583. /**
  584. * Destroys allocated elements.
  585. *
  586. * @private
  587. * @function Highcharts.Scrollbar#destroy
  588. * @return {void}
  589. */
  590. Scrollbar.prototype.destroy = function () {
  591. var scroller = this.chart.scroller;
  592. // Disconnect events added in addEvents
  593. this.removeEvents();
  594. // Destroy properties
  595. [
  596. 'track',
  597. 'scrollbarRifles',
  598. 'scrollbar',
  599. 'scrollbarGroup',
  600. 'group'
  601. ].forEach(function (prop) {
  602. if (this[prop] && this[prop].destroy) {
  603. this[prop] = this[prop].destroy();
  604. }
  605. }, this);
  606. // #6421, chart may have more scrollbars
  607. if (scroller && this === scroller.scrollbar) {
  608. scroller.scrollbar = null;
  609. // Destroy elements in collection
  610. destroyObjectProperties(scroller.scrollbarButtons);
  611. }
  612. };
  613. /**
  614. * Draw the scrollbar buttons with arrows
  615. *
  616. * @private
  617. * @function Highcharts.Scrollbar#drawScrollbarButton
  618. * @param {number} index
  619. * 0 is left, 1 is right
  620. * @return {void}
  621. */
  622. Scrollbar.prototype.drawScrollbarButton = function (index) {
  623. var scroller = this,
  624. renderer = scroller.renderer,
  625. scrollbarButtons = scroller.scrollbarButtons,
  626. options = scroller.options,
  627. size = scroller.size,
  628. group,
  629. tempElem;
  630. group = renderer.g().add(scroller.group);
  631. scrollbarButtons.push(group);
  632. // Create a rectangle for the scrollbar button
  633. tempElem = renderer.rect()
  634. .addClass('highcharts-scrollbar-button')
  635. .add(group);
  636. // Presentational attributes
  637. if (!this.chart.styledMode) {
  638. tempElem.attr({
  639. stroke: options.buttonBorderColor,
  640. 'stroke-width': options.buttonBorderWidth,
  641. fill: options.buttonBackgroundColor
  642. });
  643. }
  644. // Place the rectangle based on the rendered stroke width
  645. tempElem.attr(tempElem.crisp({
  646. x: -0.5,
  647. y: -0.5,
  648. width: size + 1,
  649. height: size + 1,
  650. r: options.buttonBorderRadius
  651. }, tempElem.strokeWidth()));
  652. // Button arrow
  653. tempElem = renderer
  654. .path(swapXY([[
  655. 'M',
  656. size / 2 + (index ? -1 : 1),
  657. size / 2 - 3
  658. ], [
  659. 'L',
  660. size / 2 + (index ? -1 : 1),
  661. size / 2 + 3
  662. ], [
  663. 'L',
  664. size / 2 + (index ? 2 : -2),
  665. size / 2
  666. ]], options.vertical))
  667. .addClass('highcharts-scrollbar-arrow')
  668. .add(scrollbarButtons[index]);
  669. if (!this.chart.styledMode) {
  670. tempElem.attr({
  671. fill: options.buttonArrowColor
  672. });
  673. }
  674. };
  675. /**
  676. * @private
  677. * @function Highcharts.Scrollbar#init
  678. * @param {Highcharts.SVGRenderer} renderer
  679. * @param {Highcharts.ScrollbarOptions} options
  680. * @param {Highcharts.Chart} chart
  681. */
  682. Scrollbar.prototype.init = function (renderer, options, chart) {
  683. this.scrollbarButtons = [];
  684. this.renderer = renderer;
  685. this.userOptions = options;
  686. this.options = merge(Scrollbar.defaultOptions, options);
  687. this.chart = chart;
  688. // backward compatibility
  689. this.size = pick(this.options.size, this.options.height);
  690. // Init
  691. if (options.enabled) {
  692. this.render();
  693. this.addEvents();
  694. }
  695. };
  696. Scrollbar.prototype.mouseDownHandler = function (e) {
  697. var scroller = this;
  698. var normalizedEvent = scroller.chart.pointer.normalize(e),
  699. mousePosition = scroller.cursorToScrollbarPosition(normalizedEvent);
  700. scroller.chartX = mousePosition.chartX;
  701. scroller.chartY = mousePosition.chartY;
  702. scroller.initPositions = [scroller.from, scroller.to];
  703. scroller.grabbedCenter = true;
  704. };
  705. /**
  706. * Event handler for the mouse move event.
  707. * @private
  708. */
  709. Scrollbar.prototype.mouseMoveHandler = function (e) {
  710. var scroller = this;
  711. var normalizedEvent = scroller.chart.pointer.normalize(e),
  712. options = scroller.options,
  713. direction = options.vertical ? 'chartY' : 'chartX',
  714. initPositions = scroller.initPositions || [],
  715. scrollPosition,
  716. chartPosition,
  717. change;
  718. // In iOS, a mousemove event with e.pageX === 0 is fired when
  719. // holding the finger down in the center of the scrollbar. This
  720. // should be ignored.
  721. if (scroller.grabbedCenter &&
  722. // #4696, scrollbar failed on Android
  723. (!e.touches || e.touches[0][direction] !== 0)) {
  724. chartPosition = scroller.cursorToScrollbarPosition(normalizedEvent)[direction];
  725. scrollPosition = scroller[direction];
  726. change = chartPosition - scrollPosition;
  727. scroller.hasDragged = true;
  728. scroller.updatePosition(initPositions[0] + change, initPositions[1] + change);
  729. if (scroller.hasDragged) {
  730. fireEvent(scroller, 'changed', {
  731. from: scroller.from,
  732. to: scroller.to,
  733. trigger: 'scrollbar',
  734. DOMType: e.type,
  735. DOMEvent: e
  736. });
  737. }
  738. }
  739. };
  740. /**
  741. * Event handler for the mouse up event.
  742. * @private
  743. */
  744. Scrollbar.prototype.mouseUpHandler = function (e) {
  745. var scroller = this;
  746. if (scroller.hasDragged) {
  747. fireEvent(scroller, 'changed', {
  748. from: scroller.from,
  749. to: scroller.to,
  750. trigger: 'scrollbar',
  751. DOMType: e.type,
  752. DOMEvent: e
  753. });
  754. }
  755. scroller.grabbedCenter =
  756. scroller.hasDragged =
  757. scroller.chartX =
  758. scroller.chartY = null;
  759. };
  760. /**
  761. * Position the scrollbar, method called from a parent with defined
  762. * dimensions.
  763. *
  764. * @private
  765. * @function Highcharts.Scrollbar#position
  766. * @param {number} x
  767. * x-position on the chart
  768. * @param {number} y
  769. * y-position on the chart
  770. * @param {number} width
  771. * width of the scrollbar
  772. * @param {number} height
  773. * height of the scorllbar
  774. * @return {void}
  775. */
  776. Scrollbar.prototype.position = function (x, y, width, height) {
  777. var scroller = this,
  778. options = scroller.options,
  779. vertical = options.vertical,
  780. xOffset = height,
  781. yOffset = 0,
  782. method = scroller.rendered ? 'animate' : 'attr';
  783. scroller.x = x;
  784. scroller.y = y + this.trackBorderWidth;
  785. scroller.width = width; // width with buttons
  786. scroller.height = height;
  787. scroller.xOffset = xOffset;
  788. scroller.yOffset = yOffset;
  789. // If Scrollbar is a vertical type, swap options:
  790. if (vertical) {
  791. scroller.width = scroller.yOffset = width = yOffset = scroller.size;
  792. scroller.xOffset = xOffset = 0;
  793. scroller.barWidth = height - width * 2; // width without buttons
  794. scroller.x = x = x + scroller.options.margin;
  795. }
  796. else {
  797. scroller.height = scroller.xOffset = height = xOffset =
  798. scroller.size;
  799. scroller.barWidth = width - height * 2; // width without buttons
  800. scroller.y = scroller.y + scroller.options.margin;
  801. }
  802. // Set general position for a group:
  803. scroller.group[method]({
  804. translateX: x,
  805. translateY: scroller.y
  806. });
  807. // Resize background/track:
  808. scroller.track[method]({
  809. width: width,
  810. height: height
  811. });
  812. // Move right/bottom button ot it's place:
  813. scroller.scrollbarButtons[1][method]({
  814. translateX: vertical ? 0 : width - xOffset,
  815. translateY: vertical ? height - yOffset : 0
  816. });
  817. };
  818. /**
  819. * Removes the event handlers attached previously with addEvents.
  820. *
  821. * @private
  822. * @function Highcharts.Scrollbar#removeEvents
  823. * @return {void}
  824. */
  825. Scrollbar.prototype.removeEvents = function () {
  826. this._events.forEach(function (args) {
  827. removeEvent.apply(null, args);
  828. });
  829. this._events.length = 0;
  830. };
  831. /**
  832. * Render scrollbar with all required items.
  833. *
  834. * @private
  835. * @function Highcharts.Scrollbar#render
  836. */
  837. Scrollbar.prototype.render = function () {
  838. var scroller = this,
  839. renderer = scroller.renderer,
  840. options = scroller.options,
  841. size = scroller.size,
  842. styledMode = this.chart.styledMode,
  843. group;
  844. // Draw the scrollbar group
  845. scroller.group = group = renderer.g('scrollbar').attr({
  846. zIndex: options.zIndex,
  847. translateY: -99999
  848. }).add();
  849. // Draw the scrollbar track:
  850. scroller.track = renderer.rect()
  851. .addClass('highcharts-scrollbar-track')
  852. .attr({
  853. x: 0,
  854. r: options.trackBorderRadius || 0,
  855. height: size,
  856. width: size
  857. }).add(group);
  858. if (!styledMode) {
  859. scroller.track.attr({
  860. fill: options.trackBackgroundColor,
  861. stroke: options.trackBorderColor,
  862. 'stroke-width': options.trackBorderWidth
  863. });
  864. }
  865. this.trackBorderWidth = scroller.track.strokeWidth();
  866. scroller.track.attr({
  867. y: -this.trackBorderWidth % 2 / 2
  868. });
  869. // Draw the scrollbar itself
  870. scroller.scrollbarGroup = renderer.g().add(group);
  871. scroller.scrollbar = renderer.rect()
  872. .addClass('highcharts-scrollbar-thumb')
  873. .attr({
  874. height: size,
  875. width: size,
  876. r: options.barBorderRadius || 0
  877. }).add(scroller.scrollbarGroup);
  878. scroller.scrollbarRifles = renderer
  879. .path(swapXY([
  880. ['M', -3, size / 4],
  881. ['L', -3, 2 * size / 3],
  882. ['M', 0, size / 4],
  883. ['L', 0, 2 * size / 3],
  884. ['M', 3, size / 4],
  885. ['L', 3, 2 * size / 3]
  886. ], options.vertical))
  887. .addClass('highcharts-scrollbar-rifles')
  888. .add(scroller.scrollbarGroup);
  889. if (!styledMode) {
  890. scroller.scrollbar.attr({
  891. fill: options.barBackgroundColor,
  892. stroke: options.barBorderColor,
  893. 'stroke-width': options.barBorderWidth
  894. });
  895. scroller.scrollbarRifles.attr({
  896. stroke: options.rifleColor,
  897. 'stroke-width': 1
  898. });
  899. }
  900. scroller.scrollbarStrokeWidth = scroller.scrollbar.strokeWidth();
  901. scroller.scrollbarGroup.translate(-scroller.scrollbarStrokeWidth % 2 / 2, -scroller.scrollbarStrokeWidth % 2 / 2);
  902. // Draw the buttons:
  903. scroller.drawScrollbarButton(0);
  904. scroller.drawScrollbarButton(1);
  905. };
  906. /**
  907. * Set scrollbar size, with a given scale.
  908. *
  909. * @private
  910. * @function Highcharts.Scrollbar#setRange
  911. * @param {number} from
  912. * scale (0-1) where bar should start
  913. * @param {number} to
  914. * scale (0-1) where bar should end
  915. * @return {void}
  916. */
  917. Scrollbar.prototype.setRange = function (from, to) {
  918. var scroller = this,
  919. options = scroller.options,
  920. vertical = options.vertical,
  921. minWidth = options.minWidth,
  922. fullWidth = scroller.barWidth,
  923. fromPX,
  924. toPX,
  925. newPos,
  926. newSize,
  927. newRiflesPos,
  928. method = (this.rendered &&
  929. !this.hasDragged &&
  930. !(this.chart.navigator && this.chart.navigator.hasDragged)) ? 'animate' : 'attr';
  931. if (!defined(fullWidth)) {
  932. return;
  933. }
  934. from = Math.max(from, 0);
  935. fromPX = Math.ceil(fullWidth * from);
  936. toPX = fullWidth * Math.min(to, 1);
  937. scroller.calculatedWidth = newSize = correctFloat(toPX - fromPX);
  938. // We need to recalculate position, if minWidth is used
  939. if (newSize < minWidth) {
  940. fromPX = (fullWidth - minWidth + newSize) * from;
  941. newSize = minWidth;
  942. }
  943. newPos = Math.floor(fromPX + scroller.xOffset + scroller.yOffset);
  944. newRiflesPos = newSize / 2 - 0.5; // -0.5 -> rifle line width / 2
  945. // Store current position:
  946. scroller.from = from;
  947. scroller.to = to;
  948. if (!vertical) {
  949. scroller.scrollbarGroup[method]({
  950. translateX: newPos
  951. });
  952. scroller.scrollbar[method]({
  953. width: newSize
  954. });
  955. scroller.scrollbarRifles[method]({
  956. translateX: newRiflesPos
  957. });
  958. scroller.scrollbarLeft = newPos;
  959. scroller.scrollbarTop = 0;
  960. }
  961. else {
  962. scroller.scrollbarGroup[method]({
  963. translateY: newPos
  964. });
  965. scroller.scrollbar[method]({
  966. height: newSize
  967. });
  968. scroller.scrollbarRifles[method]({
  969. translateY: newRiflesPos
  970. });
  971. scroller.scrollbarTop = newPos;
  972. scroller.scrollbarLeft = 0;
  973. }
  974. if (newSize <= 12) {
  975. scroller.scrollbarRifles.hide();
  976. }
  977. else {
  978. scroller.scrollbarRifles.show(true);
  979. }
  980. // Show or hide the scrollbar based on the showFull setting
  981. if (options.showFull === false) {
  982. if (from <= 0 && to >= 1) {
  983. scroller.group.hide();
  984. }
  985. else {
  986. scroller.group.show();
  987. }
  988. }
  989. scroller.rendered = true;
  990. };
  991. Scrollbar.prototype.trackClick = function (e) {
  992. var scroller = this;
  993. var normalizedEvent = scroller.chart.pointer.normalize(e),
  994. range = scroller.to - scroller.from,
  995. top = scroller.y + scroller.scrollbarTop,
  996. left = scroller.x + scroller.scrollbarLeft;
  997. if ((scroller.options.vertical && normalizedEvent.chartY > top) ||
  998. (!scroller.options.vertical && normalizedEvent.chartX > left)) {
  999. // On the top or on the left side of the track:
  1000. scroller.updatePosition(scroller.from + range, scroller.to + range);
  1001. }
  1002. else {
  1003. // On the bottom or the right side of the track:
  1004. scroller.updatePosition(scroller.from - range, scroller.to - range);
  1005. }
  1006. fireEvent(scroller, 'changed', {
  1007. from: scroller.from,
  1008. to: scroller.to,
  1009. trigger: 'scrollbar',
  1010. DOMEvent: e
  1011. });
  1012. };
  1013. /**
  1014. * Update the scrollbar with new options
  1015. *
  1016. * @private
  1017. * @function Highcharts.Scrollbar#update
  1018. * @param {Highcharts.ScrollbarOptions} options
  1019. * @return {void}
  1020. */
  1021. Scrollbar.prototype.update = function (options) {
  1022. this.destroy();
  1023. this.init(this.chart.renderer, merge(true, this.options, options), this.chart);
  1024. };
  1025. /**
  1026. * Update position option in the Scrollbar, with normalized 0-1 scale
  1027. *
  1028. * @private
  1029. * @function Highcharts.Scrollbar#updatePosition
  1030. * @param {number} from
  1031. * @param {number} to
  1032. * @return {void}
  1033. */
  1034. Scrollbar.prototype.updatePosition = function (from, to) {
  1035. if (to > 1) {
  1036. from = correctFloat(1 - correctFloat(to - from));
  1037. to = 1;
  1038. }
  1039. if (from < 0) {
  1040. to = correctFloat(to - from);
  1041. from = 0;
  1042. }
  1043. this.from = from;
  1044. this.to = to;
  1045. };
  1046. /* *
  1047. *
  1048. * Static Properties
  1049. *
  1050. * */
  1051. /**
  1052. *
  1053. * The scrollbar is a means of panning over the X axis of a stock chart.
  1054. * Scrollbars can also be applied to other types of axes.
  1055. *
  1056. * Another approach to scrollable charts is the [chart.scrollablePlotArea](
  1057. * https://api.highcharts.com/highcharts/chart.scrollablePlotArea) option that
  1058. * is especially suitable for simpler cartesian charts on mobile.
  1059. *
  1060. * In styled mode, all the presentational options for the
  1061. * scrollbar are replaced by the classes `.highcharts-scrollbar-thumb`,
  1062. * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`,
  1063. * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`.
  1064. *
  1065. * @sample stock/yaxis/inverted-bar-scrollbar/
  1066. * A scrollbar on a simple bar chart
  1067. *
  1068. * @product highstock gantt
  1069. * @optionparent scrollbar
  1070. *
  1071. * @private
  1072. */
  1073. Scrollbar.defaultOptions = {
  1074. /**
  1075. * The height of the scrollbar. The height also applies to the width
  1076. * of the scroll arrows so that they are always squares. Defaults to
  1077. * 20 for touch devices and 14 for mouse devices.
  1078. *
  1079. * @sample stock/scrollbar/height/
  1080. * A 30px scrollbar
  1081. *
  1082. * @type {number}
  1083. * @default 20/14
  1084. */
  1085. height: isTouchDevice ? 20 : 14,
  1086. /**
  1087. * The border rounding radius of the bar.
  1088. *
  1089. * @sample stock/scrollbar/style/
  1090. * Scrollbar styling
  1091. */
  1092. barBorderRadius: 0,
  1093. /**
  1094. * The corner radius of the scrollbar buttons.
  1095. *
  1096. * @sample stock/scrollbar/style/
  1097. * Scrollbar styling
  1098. */
  1099. buttonBorderRadius: 0,
  1100. /**
  1101. * Enable or disable the scrollbar.
  1102. *
  1103. * @sample stock/scrollbar/enabled/
  1104. * Disable the scrollbar, only use navigator
  1105. *
  1106. * @type {boolean}
  1107. * @default true
  1108. * @apioption scrollbar.enabled
  1109. */
  1110. /**
  1111. * Whether to redraw the main chart as the scrollbar or the navigator
  1112. * zoomed window is moved. Defaults to `true` for modern browsers and
  1113. * `false` for legacy IE browsers as well as mobile devices.
  1114. *
  1115. * @sample stock/scrollbar/liveredraw
  1116. * Setting live redraw to false
  1117. *
  1118. * @type {boolean}
  1119. * @since 1.3
  1120. */
  1121. liveRedraw: void 0,
  1122. /**
  1123. * The margin between the scrollbar and its axis when the scrollbar is
  1124. * applied directly to an axis.
  1125. */
  1126. margin: 10,
  1127. /**
  1128. * The minimum width of the scrollbar.
  1129. *
  1130. * @since 1.2.5
  1131. */
  1132. minWidth: 6,
  1133. /**
  1134. * Whether to show or hide the scrollbar when the scrolled content is
  1135. * zoomed out to it full extent.
  1136. *
  1137. * @type {boolean}
  1138. * @default true
  1139. * @apioption scrollbar.showFull
  1140. */
  1141. step: 0.2,
  1142. /**
  1143. * The z index of the scrollbar group.
  1144. */
  1145. zIndex: 3,
  1146. /**
  1147. * The background color of the scrollbar itself.
  1148. *
  1149. * @sample stock/scrollbar/style/
  1150. * Scrollbar styling
  1151. *
  1152. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1153. */
  1154. barBackgroundColor: '#cccccc',
  1155. /**
  1156. * The width of the bar's border.
  1157. *
  1158. * @sample stock/scrollbar/style/
  1159. * Scrollbar styling
  1160. */
  1161. barBorderWidth: 1,
  1162. /**
  1163. * The color of the scrollbar's border.
  1164. *
  1165. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1166. */
  1167. barBorderColor: '#cccccc',
  1168. /**
  1169. * The color of the small arrow inside the scrollbar buttons.
  1170. *
  1171. * @sample stock/scrollbar/style/
  1172. * Scrollbar styling
  1173. *
  1174. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1175. */
  1176. buttonArrowColor: '#333333',
  1177. /**
  1178. * The color of scrollbar buttons.
  1179. *
  1180. * @sample stock/scrollbar/style/
  1181. * Scrollbar styling
  1182. *
  1183. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1184. */
  1185. buttonBackgroundColor: '#e6e6e6',
  1186. /**
  1187. * The color of the border of the scrollbar buttons.
  1188. *
  1189. * @sample stock/scrollbar/style/
  1190. * Scrollbar styling
  1191. *
  1192. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1193. */
  1194. buttonBorderColor: '#cccccc',
  1195. /**
  1196. * The border width of the scrollbar buttons.
  1197. *
  1198. * @sample stock/scrollbar/style/
  1199. * Scrollbar styling
  1200. */
  1201. buttonBorderWidth: 1,
  1202. /**
  1203. * The color of the small rifles in the middle of the scrollbar.
  1204. *
  1205. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1206. */
  1207. rifleColor: '#333333',
  1208. /**
  1209. * The color of the track background.
  1210. *
  1211. * @sample stock/scrollbar/style/
  1212. * Scrollbar styling
  1213. *
  1214. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1215. */
  1216. trackBackgroundColor: '#f2f2f2',
  1217. /**
  1218. * The color of the border of the scrollbar track.
  1219. *
  1220. * @sample stock/scrollbar/style/
  1221. * Scrollbar styling
  1222. *
  1223. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1224. */
  1225. trackBorderColor: '#f2f2f2',
  1226. /**
  1227. * The corner radius of the border of the scrollbar track.
  1228. *
  1229. * @sample stock/scrollbar/style/
  1230. * Scrollbar styling
  1231. *
  1232. * @type {number}
  1233. * @default 0
  1234. * @apioption scrollbar.trackBorderRadius
  1235. */
  1236. /**
  1237. * The width of the border of the scrollbar track.
  1238. *
  1239. * @sample stock/scrollbar/style/
  1240. * Scrollbar styling
  1241. */
  1242. trackBorderWidth: 1
  1243. };
  1244. return Scrollbar;
  1245. }());
  1246. if (!H.Scrollbar) {
  1247. defaultOptions.scrollbar = merge(true, Scrollbar.defaultOptions, defaultOptions.scrollbar);
  1248. H.Scrollbar = Scrollbar;
  1249. ScrollbarAxis.compose(Axis, Scrollbar);
  1250. }
  1251. return H.Scrollbar;
  1252. });
  1253. _registerModule(_modules, 'Core/Navigator.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Color.js'], _modules['Core/Globals.js'], _modules['Core/Axis/NavigatorAxis.js'], _modules['Core/Options.js'], _modules['Core/Scrollbar.js'], _modules['Core/Utilities.js']], function (Axis, Chart, Color, H, NavigatorAxis, O, Scrollbar, U) {
  1254. /* *
  1255. *
  1256. * (c) 2010-2020 Torstein Honsi
  1257. *
  1258. * License: www.highcharts.com/license
  1259. *
  1260. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  1261. *
  1262. * */
  1263. var color = Color.parse;
  1264. var defaultOptions = O.defaultOptions;
  1265. var addEvent = U.addEvent,
  1266. clamp = U.clamp,
  1267. correctFloat = U.correctFloat,
  1268. defined = U.defined,
  1269. destroyObjectProperties = U.destroyObjectProperties,
  1270. erase = U.erase,
  1271. extend = U.extend,
  1272. find = U.find,
  1273. isArray = U.isArray,
  1274. isNumber = U.isNumber,
  1275. merge = U.merge,
  1276. pick = U.pick,
  1277. removeEvent = U.removeEvent,
  1278. splat = U.splat;
  1279. var hasTouch = H.hasTouch,
  1280. isTouchDevice = H.isTouchDevice,
  1281. Series = H.Series,
  1282. seriesTypes = H.seriesTypes,
  1283. defaultSeriesType,
  1284. // Finding the min or max of a set of variables where we don't know if they
  1285. // are defined, is a pattern that is repeated several places in Highcharts.
  1286. // Consider making this a global utility method.
  1287. numExt = function (extreme) {
  1288. var args = [];
  1289. for (var _i = 1; _i < arguments.length; _i++) {
  1290. args[_i - 1] = arguments[_i];
  1291. }
  1292. var numbers = [].filter.call(args,
  1293. isNumber);
  1294. if (numbers.length) {
  1295. return Math[extreme].apply(0, numbers);
  1296. }
  1297. };
  1298. defaultSeriesType = typeof seriesTypes.areaspline === 'undefined' ?
  1299. 'line' :
  1300. 'areaspline';
  1301. extend(defaultOptions, {
  1302. /**
  1303. * Maximum range which can be set using the navigator's handles.
  1304. * Opposite of [xAxis.minRange](#xAxis.minRange).
  1305. *
  1306. * @sample {highstock} stock/navigator/maxrange/
  1307. * Defined max and min range
  1308. *
  1309. * @type {number}
  1310. * @since 6.0.0
  1311. * @product highstock gantt
  1312. * @apioption xAxis.maxRange
  1313. */
  1314. /**
  1315. * The navigator is a small series below the main series, displaying
  1316. * a view of the entire data set. It provides tools to zoom in and
  1317. * out on parts of the data as well as panning across the dataset.
  1318. *
  1319. * @product highstock gantt
  1320. * @optionparent navigator
  1321. */
  1322. navigator: {
  1323. /**
  1324. * Whether the navigator and scrollbar should adapt to updated data
  1325. * in the base X axis. When loading data async, as in the demo below,
  1326. * this should be `false`. Otherwise new data will trigger navigator
  1327. * redraw, which will cause unwanted looping. In the demo below, the
  1328. * data in the navigator is set only once. On navigating, only the main
  1329. * chart content is updated.
  1330. *
  1331. * @sample {highstock} stock/demo/lazy-loading/
  1332. * Set to false with async data loading
  1333. *
  1334. * @type {boolean}
  1335. * @default true
  1336. * @apioption navigator.adaptToUpdatedData
  1337. */
  1338. /**
  1339. * An integer identifying the index to use for the base series, or a
  1340. * string representing the id of the series.
  1341. *
  1342. * **Note**: As of Highcharts 5.0, this is now a deprecated option.
  1343. * Prefer [series.showInNavigator](#plotOptions.series.showInNavigator).
  1344. *
  1345. * @see [series.showInNavigator](#plotOptions.series.showInNavigator)
  1346. *
  1347. * @deprecated
  1348. * @type {number|string}
  1349. * @default 0
  1350. * @apioption navigator.baseSeries
  1351. */
  1352. /**
  1353. * Enable or disable the navigator.
  1354. *
  1355. * @sample {highstock} stock/navigator/enabled/
  1356. * Disable the navigator
  1357. *
  1358. * @type {boolean}
  1359. * @default true
  1360. * @apioption navigator.enabled
  1361. */
  1362. /**
  1363. * When the chart is inverted, whether to draw the navigator on the
  1364. * opposite side.
  1365. *
  1366. * @type {boolean}
  1367. * @default false
  1368. * @since 5.0.8
  1369. * @apioption navigator.opposite
  1370. */
  1371. /**
  1372. * The height of the navigator.
  1373. *
  1374. * @sample {highstock} stock/navigator/height/
  1375. * A higher navigator
  1376. */
  1377. height: 40,
  1378. /**
  1379. * The distance from the nearest element, the X axis or X axis labels.
  1380. *
  1381. * @sample {highstock} stock/navigator/margin/
  1382. * A margin of 2 draws the navigator closer to the X axis labels
  1383. */
  1384. margin: 25,
  1385. /**
  1386. * Whether the mask should be inside the range marking the zoomed
  1387. * range, or outside. In Highstock 1.x it was always `false`.
  1388. *
  1389. * @sample {highstock} stock/navigator/maskinside-false/
  1390. * False, mask outside
  1391. *
  1392. * @since 2.0
  1393. */
  1394. maskInside: true,
  1395. /**
  1396. * Options for the handles for dragging the zoomed area.
  1397. *
  1398. * @sample {highstock} stock/navigator/handles/
  1399. * Colored handles
  1400. */
  1401. handles: {
  1402. /**
  1403. * Width for handles.
  1404. *
  1405. * @sample {highstock} stock/navigator/styled-handles/
  1406. * Styled handles
  1407. *
  1408. * @since 6.0.0
  1409. */
  1410. width: 7,
  1411. /**
  1412. * Height for handles.
  1413. *
  1414. * @sample {highstock} stock/navigator/styled-handles/
  1415. * Styled handles
  1416. *
  1417. * @since 6.0.0
  1418. */
  1419. height: 15,
  1420. /**
  1421. * Array to define shapes of handles. 0-index for left, 1-index for
  1422. * right.
  1423. *
  1424. * Additionally, the URL to a graphic can be given on this form:
  1425. * `url(graphic.png)`. Note that for the image to be applied to
  1426. * exported charts, its URL needs to be accessible by the export
  1427. * server.
  1428. *
  1429. * Custom callbacks for symbol path generation can also be added to
  1430. * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
  1431. * used by its method name, as shown in the demo.
  1432. *
  1433. * @sample {highstock} stock/navigator/styled-handles/
  1434. * Styled handles
  1435. *
  1436. * @type {Array<string>}
  1437. * @default ["navigator-handle", "navigator-handle"]
  1438. * @since 6.0.0
  1439. */
  1440. symbols: ['navigator-handle', 'navigator-handle'],
  1441. /**
  1442. * Allows to enable/disable handles.
  1443. *
  1444. * @since 6.0.0
  1445. */
  1446. enabled: true,
  1447. /**
  1448. * The width for the handle border and the stripes inside.
  1449. *
  1450. * @sample {highstock} stock/navigator/styled-handles/
  1451. * Styled handles
  1452. *
  1453. * @since 6.0.0
  1454. * @apioption navigator.handles.lineWidth
  1455. */
  1456. lineWidth: 1,
  1457. /**
  1458. * The fill for the handle.
  1459. *
  1460. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1461. */
  1462. backgroundColor: '#f2f2f2',
  1463. /**
  1464. * The stroke for the handle border and the stripes inside.
  1465. *
  1466. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1467. */
  1468. borderColor: '#999999'
  1469. },
  1470. /**
  1471. * The color of the mask covering the areas of the navigator series
  1472. * that are currently not visible in the main series. The default
  1473. * color is bluish with an opacity of 0.3 to see the series below.
  1474. *
  1475. * @see In styled mode, the mask is styled with the
  1476. * `.highcharts-navigator-mask` and
  1477. * `.highcharts-navigator-mask-inside` classes.
  1478. *
  1479. * @sample {highstock} stock/navigator/maskfill/
  1480. * Blue, semi transparent mask
  1481. *
  1482. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1483. * @default rgba(102,133,194,0.3)
  1484. */
  1485. maskFill: color('#6685c2').setOpacity(0.3).get(),
  1486. /**
  1487. * The color of the line marking the currently zoomed area in the
  1488. * navigator.
  1489. *
  1490. * @sample {highstock} stock/navigator/outline/
  1491. * 2px blue outline
  1492. *
  1493. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1494. * @default #cccccc
  1495. */
  1496. outlineColor: '#cccccc',
  1497. /**
  1498. * The width of the line marking the currently zoomed area in the
  1499. * navigator.
  1500. *
  1501. * @see In styled mode, the outline stroke width is set with the
  1502. * `.highcharts-navigator-outline` class.
  1503. *
  1504. * @sample {highstock} stock/navigator/outline/
  1505. * 2px blue outline
  1506. *
  1507. * @type {number}
  1508. */
  1509. outlineWidth: 1,
  1510. /**
  1511. * Options for the navigator series. Available options are the same
  1512. * as any series, documented at [plotOptions](#plotOptions.series)
  1513. * and [series](#series).
  1514. *
  1515. * Unless data is explicitly defined on navigator.series, the data
  1516. * is borrowed from the first series in the chart.
  1517. *
  1518. * Default series options for the navigator series are:
  1519. * ```js
  1520. * series: {
  1521. * type: 'areaspline',
  1522. * fillOpacity: 0.05,
  1523. * dataGrouping: {
  1524. * smoothed: true
  1525. * },
  1526. * lineWidth: 1,
  1527. * marker: {
  1528. * enabled: false
  1529. * }
  1530. * }
  1531. * ```
  1532. *
  1533. * @see In styled mode, the navigator series is styled with the
  1534. * `.highcharts-navigator-series` class.
  1535. *
  1536. * @sample {highstock} stock/navigator/series-data/
  1537. * Using a separate data set for the navigator
  1538. * @sample {highstock} stock/navigator/series/
  1539. * A green navigator series
  1540. *
  1541. * @type {*|Array<*>|Highcharts.SeriesOptionsType|Array<Highcharts.SeriesOptionsType>}
  1542. */
  1543. series: {
  1544. /**
  1545. * The type of the navigator series.
  1546. *
  1547. * Heads up:
  1548. * In column-type navigator, zooming is limited to at least one
  1549. * point with its `pointRange`.
  1550. *
  1551. * @sample {highstock} stock/navigator/column/
  1552. * Column type navigator
  1553. *
  1554. * @type {string}
  1555. * @default {highstock} `areaspline` if defined, otherwise `line`
  1556. * @default {gantt} gantt
  1557. */
  1558. type: defaultSeriesType,
  1559. /**
  1560. * The fill opacity of the navigator series.
  1561. */
  1562. fillOpacity: 0.05,
  1563. /**
  1564. * The pixel line width of the navigator series.
  1565. */
  1566. lineWidth: 1,
  1567. /**
  1568. * @ignore-option
  1569. */
  1570. compare: null,
  1571. /**
  1572. * Unless data is explicitly defined, the data is borrowed from the
  1573. * first series in the chart.
  1574. *
  1575. * @type {Array<number|Array<number|string|null>|object|null>}
  1576. * @product highstock
  1577. * @apioption navigator.series.data
  1578. */
  1579. /**
  1580. * Data grouping options for the navigator series.
  1581. *
  1582. * @extends plotOptions.series.dataGrouping
  1583. */
  1584. dataGrouping: {
  1585. approximation: 'average',
  1586. enabled: true,
  1587. groupPixelWidth: 2,
  1588. smoothed: true,
  1589. // Day and week differs from plotOptions.series.dataGrouping
  1590. units: [
  1591. ['millisecond', [1, 2, 5, 10, 20, 25, 50, 100, 200, 500]],
  1592. ['second', [1, 2, 5, 10, 15, 30]],
  1593. ['minute', [1, 2, 5, 10, 15, 30]],
  1594. ['hour', [1, 2, 3, 4, 6, 8, 12]],
  1595. ['day', [1, 2, 3, 4]],
  1596. ['week', [1, 2, 3]],
  1597. ['month', [1, 3, 6]],
  1598. ['year', null]
  1599. ]
  1600. },
  1601. /**
  1602. * Data label options for the navigator series. Data labels are
  1603. * disabled by default on the navigator series.
  1604. *
  1605. * @extends plotOptions.series.dataLabels
  1606. */
  1607. dataLabels: {
  1608. enabled: false,
  1609. zIndex: 2 // #1839
  1610. },
  1611. id: 'highcharts-navigator-series',
  1612. className: 'highcharts-navigator-series',
  1613. /**
  1614. * Sets the fill color of the navigator series.
  1615. *
  1616. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1617. * @apioption navigator.series.color
  1618. */
  1619. /**
  1620. * Line color for the navigator series. Allows setting the color
  1621. * while disallowing the default candlestick setting.
  1622. *
  1623. * @type {Highcharts.ColorString|null}
  1624. */
  1625. lineColor: null,
  1626. marker: {
  1627. enabled: false
  1628. },
  1629. /**
  1630. * Since Highstock v8, default value is the same as default
  1631. * `pointRange` defined for a specific type (e.g. `null` for
  1632. * column type).
  1633. *
  1634. * In Highstock version < 8, defaults to 0.
  1635. *
  1636. * @extends plotOptions.series.pointRange
  1637. * @type {number|null}
  1638. * @apioption navigator.series.pointRange
  1639. */
  1640. /**
  1641. * The threshold option. Setting it to 0 will make the default
  1642. * navigator area series draw its area from the 0 value and up.
  1643. *
  1644. * @type {number|null}
  1645. */
  1646. threshold: null
  1647. },
  1648. /**
  1649. * Options for the navigator X axis. Default series options for the
  1650. * navigator xAxis are:
  1651. * ```js
  1652. * xAxis: {
  1653. * tickWidth: 0,
  1654. * lineWidth: 0,
  1655. * gridLineWidth: 1,
  1656. * tickPixelInterval: 200,
  1657. * labels: {
  1658. * align: 'left',
  1659. * style: {
  1660. * color: '#888'
  1661. * },
  1662. * x: 3,
  1663. * y: -4
  1664. * }
  1665. * }
  1666. * ```
  1667. *
  1668. * @extends xAxis
  1669. * @excluding linkedTo, maxZoom, minRange, opposite, range, scrollbar,
  1670. * showEmpty, maxRange
  1671. */
  1672. xAxis: {
  1673. /**
  1674. * Additional range on the right side of the xAxis. Works similar to
  1675. * xAxis.maxPadding, but value is set in milliseconds.
  1676. * Can be set for both, main xAxis and navigator's xAxis.
  1677. *
  1678. * @since 6.0.0
  1679. */
  1680. overscroll: 0,
  1681. className: 'highcharts-navigator-xaxis',
  1682. tickLength: 0,
  1683. lineWidth: 0,
  1684. gridLineColor: '#e6e6e6',
  1685. gridLineWidth: 1,
  1686. tickPixelInterval: 200,
  1687. labels: {
  1688. align: 'left',
  1689. /**
  1690. * @type {Highcharts.CSSObject}
  1691. */
  1692. style: {
  1693. /** @ignore */
  1694. color: '#999999'
  1695. },
  1696. x: 3,
  1697. y: -4
  1698. },
  1699. crosshair: false
  1700. },
  1701. /**
  1702. * Options for the navigator Y axis. Default series options for the
  1703. * navigator yAxis are:
  1704. * ```js
  1705. * yAxis: {
  1706. * gridLineWidth: 0,
  1707. * startOnTick: false,
  1708. * endOnTick: false,
  1709. * minPadding: 0.1,
  1710. * maxPadding: 0.1,
  1711. * labels: {
  1712. * enabled: false
  1713. * },
  1714. * title: {
  1715. * text: null
  1716. * },
  1717. * tickWidth: 0
  1718. * }
  1719. * ```
  1720. *
  1721. * @extends yAxis
  1722. * @excluding height, linkedTo, maxZoom, minRange, ordinal, range,
  1723. * showEmpty, scrollbar, top, units, maxRange, minLength,
  1724. * maxLength, resize
  1725. */
  1726. yAxis: {
  1727. className: 'highcharts-navigator-yaxis',
  1728. gridLineWidth: 0,
  1729. startOnTick: false,
  1730. endOnTick: false,
  1731. minPadding: 0.1,
  1732. maxPadding: 0.1,
  1733. labels: {
  1734. enabled: false
  1735. },
  1736. crosshair: false,
  1737. title: {
  1738. text: null
  1739. },
  1740. tickLength: 0,
  1741. tickWidth: 0
  1742. }
  1743. }
  1744. });
  1745. /* eslint-disable no-invalid-this, valid-jsdoc */
  1746. /**
  1747. * Draw one of the handles on the side of the zoomed range in the navigator
  1748. *
  1749. * @private
  1750. * @function Highcharts.Renderer#symbols.navigator-handle
  1751. * @param {number} x
  1752. * @param {number} y
  1753. * @param {number} w
  1754. * @param {number} h
  1755. * @param {Highcharts.NavigatorHandlesOptions} options
  1756. * @return {Highcharts.SVGPathArray}
  1757. * Path to be used in a handle
  1758. */
  1759. H.Renderer.prototype.symbols['navigator-handle'] = function (x, y, w, h, options) {
  1760. var halfWidth = (options && options.width || 0) / 2,
  1761. markerPosition = Math.round(halfWidth / 3) + 0.5,
  1762. height = options && options.height || 0;
  1763. return [
  1764. ['M', -halfWidth - 1, 0.5],
  1765. ['L', halfWidth, 0.5],
  1766. ['L', halfWidth, height + 0.5],
  1767. ['L', -halfWidth - 1, height + 0.5],
  1768. ['L', -halfWidth - 1, 0.5],
  1769. ['M', -markerPosition, 4],
  1770. ['L', -markerPosition, height - 3],
  1771. ['M', markerPosition - 1, 4],
  1772. ['L', markerPosition - 1, height - 3]
  1773. ];
  1774. };
  1775. /**
  1776. * The Navigator class
  1777. *
  1778. * @private
  1779. * @class
  1780. * @name Highcharts.Navigator
  1781. *
  1782. * @param {Highcharts.Chart} chart
  1783. * Chart object
  1784. */
  1785. var Navigator = /** @class */ (function () {
  1786. function Navigator(chart) {
  1787. this.baseSeries = void 0;
  1788. this.chart = void 0;
  1789. this.handles = void 0;
  1790. this.height = void 0;
  1791. this.left = void 0;
  1792. this.navigatorEnabled = void 0;
  1793. this.navigatorGroup = void 0;
  1794. this.navigatorOptions = void 0;
  1795. this.navigatorSeries = void 0;
  1796. this.navigatorSize = void 0;
  1797. this.opposite = void 0;
  1798. this.outline = void 0;
  1799. this.outlineHeight = void 0;
  1800. this.range = void 0;
  1801. this.rendered = void 0;
  1802. this.shades = void 0;
  1803. this.size = void 0;
  1804. this.top = void 0;
  1805. this.xAxis = void 0;
  1806. this.yAxis = void 0;
  1807. this.zoomedMax = void 0;
  1808. this.zoomedMin = void 0;
  1809. this.init(chart);
  1810. }
  1811. /**
  1812. * Draw one of the handles on the side of the zoomed range in the navigator
  1813. *
  1814. * @private
  1815. * @function Highcharts.Navigator#drawHandle
  1816. *
  1817. * @param {number} x
  1818. * The x center for the handle
  1819. *
  1820. * @param {number} index
  1821. * 0 for left and 1 for right
  1822. *
  1823. * @param {boolean|undefined} inverted
  1824. * flag for chart.inverted
  1825. *
  1826. * @param {string} verb
  1827. * use 'animate' or 'attr'
  1828. */
  1829. Navigator.prototype.drawHandle = function (x, index, inverted, verb) {
  1830. var navigator = this,
  1831. height = navigator.navigatorOptions.handles.height;
  1832. // Place it
  1833. navigator.handles[index][verb](inverted ? {
  1834. translateX: Math.round(navigator.left + navigator.height / 2),
  1835. translateY: Math.round(navigator.top + parseInt(x, 10) + 0.5 - height)
  1836. } : {
  1837. translateX: Math.round(navigator.left + parseInt(x, 10)),
  1838. translateY: Math.round(navigator.top + navigator.height / 2 - height / 2 - 1)
  1839. });
  1840. };
  1841. /**
  1842. * Render outline around the zoomed range
  1843. *
  1844. * @private
  1845. * @function Highcharts.Navigator#drawOutline
  1846. *
  1847. * @param {number} zoomedMin
  1848. * in pixels position where zoomed range starts
  1849. *
  1850. * @param {number} zoomedMax
  1851. * in pixels position where zoomed range ends
  1852. *
  1853. * @param {boolean|undefined} inverted
  1854. * flag if chart is inverted
  1855. *
  1856. * @param {string} verb
  1857. * use 'animate' or 'attr'
  1858. */
  1859. Navigator.prototype.drawOutline = function (zoomedMin, zoomedMax, inverted, verb) {
  1860. var navigator = this,
  1861. maskInside = navigator.navigatorOptions.maskInside,
  1862. outlineWidth = navigator.outline.strokeWidth(),
  1863. halfOutline = outlineWidth / 2,
  1864. outlineCorrection = (outlineWidth % 2) / 2, // #5800
  1865. outlineHeight = navigator.outlineHeight,
  1866. scrollbarHeight = navigator.scrollbarHeight || 0,
  1867. navigatorSize = navigator.size,
  1868. left = navigator.left - scrollbarHeight,
  1869. navigatorTop = navigator.top,
  1870. verticalMin,
  1871. path;
  1872. if (inverted) {
  1873. left -= halfOutline;
  1874. verticalMin = navigatorTop + zoomedMax + outlineCorrection;
  1875. zoomedMax = navigatorTop + zoomedMin + outlineCorrection;
  1876. path = [
  1877. ['M', left + outlineHeight, navigatorTop - scrollbarHeight - outlineCorrection],
  1878. ['L', left + outlineHeight, verticalMin],
  1879. ['L', left, verticalMin],
  1880. ['L', left, zoomedMax],
  1881. ['L', left + outlineHeight, zoomedMax],
  1882. ['L', left + outlineHeight, navigatorTop + navigatorSize + scrollbarHeight]
  1883. ];
  1884. if (maskInside) {
  1885. path.push(['M', left + outlineHeight, verticalMin - halfOutline], // upper left of zoomed range
  1886. ['L', left + outlineHeight, zoomedMax + halfOutline] // upper right of z.r.
  1887. );
  1888. }
  1889. }
  1890. else {
  1891. zoomedMin += left + scrollbarHeight - outlineCorrection;
  1892. zoomedMax += left + scrollbarHeight - outlineCorrection;
  1893. navigatorTop += halfOutline;
  1894. path = [
  1895. ['M', left, navigatorTop],
  1896. ['L', zoomedMin, navigatorTop],
  1897. ['L', zoomedMin, navigatorTop + outlineHeight],
  1898. ['L', zoomedMax, navigatorTop + outlineHeight],
  1899. ['L', zoomedMax, navigatorTop],
  1900. ['L', left + navigatorSize + scrollbarHeight * 2, navigatorTop] // right
  1901. ];
  1902. if (maskInside) {
  1903. path.push(['M', zoomedMin - halfOutline, navigatorTop], // upper left of zoomed range
  1904. ['L', zoomedMax + halfOutline, navigatorTop] // upper right of z.r.
  1905. );
  1906. }
  1907. }
  1908. navigator.outline[verb]({
  1909. d: path
  1910. });
  1911. };
  1912. /**
  1913. * Render outline around the zoomed range
  1914. *
  1915. * @private
  1916. * @function Highcharts.Navigator#drawMasks
  1917. *
  1918. * @param {number} zoomedMin
  1919. * in pixels position where zoomed range starts
  1920. *
  1921. * @param {number} zoomedMax
  1922. * in pixels position where zoomed range ends
  1923. *
  1924. * @param {boolean|undefined} inverted
  1925. * flag if chart is inverted
  1926. *
  1927. * @param {string} verb
  1928. * use 'animate' or 'attr'
  1929. */
  1930. Navigator.prototype.drawMasks = function (zoomedMin, zoomedMax, inverted, verb) {
  1931. var navigator = this,
  1932. left = navigator.left,
  1933. top = navigator.top,
  1934. navigatorHeight = navigator.height,
  1935. height,
  1936. width,
  1937. x,
  1938. y;
  1939. // Determine rectangle position & size
  1940. // According to (non)inverted position:
  1941. if (inverted) {
  1942. x = [left, left, left];
  1943. y = [top, top + zoomedMin, top + zoomedMax];
  1944. width = [navigatorHeight, navigatorHeight, navigatorHeight];
  1945. height = [
  1946. zoomedMin,
  1947. zoomedMax - zoomedMin,
  1948. navigator.size - zoomedMax
  1949. ];
  1950. }
  1951. else {
  1952. x = [left, left + zoomedMin, left + zoomedMax];
  1953. y = [top, top, top];
  1954. width = [
  1955. zoomedMin,
  1956. zoomedMax - zoomedMin,
  1957. navigator.size - zoomedMax
  1958. ];
  1959. height = [navigatorHeight, navigatorHeight, navigatorHeight];
  1960. }
  1961. navigator.shades.forEach(function (shade, i) {
  1962. shade[verb]({
  1963. x: x[i],
  1964. y: y[i],
  1965. width: width[i],
  1966. height: height[i]
  1967. });
  1968. });
  1969. };
  1970. /**
  1971. * Generate DOM elements for a navigator:
  1972. *
  1973. * - main navigator group
  1974. *
  1975. * - all shades
  1976. *
  1977. * - outline
  1978. *
  1979. * - handles
  1980. *
  1981. * @private
  1982. * @function Highcharts.Navigator#renderElements
  1983. */
  1984. Navigator.prototype.renderElements = function () {
  1985. var navigator = this,
  1986. navigatorOptions = navigator.navigatorOptions,
  1987. maskInside = navigatorOptions.maskInside,
  1988. chart = navigator.chart,
  1989. inverted = chart.inverted,
  1990. renderer = chart.renderer,
  1991. navigatorGroup,
  1992. mouseCursor = {
  1993. cursor: inverted ? 'ns-resize' : 'ew-resize'
  1994. };
  1995. // Create the main navigator group
  1996. navigator.navigatorGroup = navigatorGroup = renderer.g('navigator')
  1997. .attr({
  1998. zIndex: 8,
  1999. visibility: 'hidden'
  2000. })
  2001. .add();
  2002. // Create masks, each mask will get events and fill:
  2003. [
  2004. !maskInside,
  2005. maskInside,
  2006. !maskInside
  2007. ].forEach(function (hasMask, index) {
  2008. navigator.shades[index] = renderer.rect()
  2009. .addClass('highcharts-navigator-mask' +
  2010. (index === 1 ? '-inside' : '-outside'))
  2011. .add(navigatorGroup);
  2012. if (!chart.styledMode) {
  2013. navigator.shades[index]
  2014. .attr({
  2015. fill: hasMask ?
  2016. navigatorOptions.maskFill :
  2017. 'rgba(0,0,0,0)'
  2018. })
  2019. .css((index === 1) && mouseCursor);
  2020. }
  2021. });
  2022. // Create the outline:
  2023. navigator.outline = renderer.path()
  2024. .addClass('highcharts-navigator-outline')
  2025. .add(navigatorGroup);
  2026. if (!chart.styledMode) {
  2027. navigator.outline.attr({
  2028. 'stroke-width': navigatorOptions.outlineWidth,
  2029. stroke: navigatorOptions.outlineColor
  2030. });
  2031. }
  2032. // Create the handlers:
  2033. if (navigatorOptions.handles.enabled) {
  2034. [0, 1].forEach(function (index) {
  2035. navigatorOptions.handles.inverted = chart.inverted;
  2036. navigator.handles[index] = renderer.symbol(navigatorOptions.handles.symbols[index], -navigatorOptions.handles.width / 2 - 1, 0, navigatorOptions.handles.width, navigatorOptions.handles.height, navigatorOptions.handles);
  2037. // zIndex = 6 for right handle, 7 for left.
  2038. // Can't be 10, because of the tooltip in inverted chart #2908
  2039. navigator.handles[index].attr({ zIndex: 7 - index })
  2040. .addClass('highcharts-navigator-handle ' +
  2041. 'highcharts-navigator-handle-' +
  2042. ['left', 'right'][index]).add(navigatorGroup);
  2043. if (!chart.styledMode) {
  2044. var handlesOptions = navigatorOptions.handles;
  2045. navigator.handles[index]
  2046. .attr({
  2047. fill: handlesOptions.backgroundColor,
  2048. stroke: handlesOptions.borderColor,
  2049. 'stroke-width': handlesOptions.lineWidth
  2050. })
  2051. .css(mouseCursor);
  2052. }
  2053. });
  2054. }
  2055. };
  2056. /**
  2057. * Update navigator
  2058. *
  2059. * @private
  2060. * @function Highcharts.Navigator#update
  2061. *
  2062. * @param {Highcharts.NavigatorOptions} options
  2063. * Options to merge in when updating navigator
  2064. */
  2065. Navigator.prototype.update = function (options) {
  2066. // Remove references to old navigator series in base series
  2067. (this.series || []).forEach(function (series) {
  2068. if (series.baseSeries) {
  2069. delete series.baseSeries.navigatorSeries;
  2070. }
  2071. });
  2072. // Destroy and rebuild navigator
  2073. this.destroy();
  2074. var chartOptions = this.chart.options;
  2075. merge(true, chartOptions.navigator, this.options, options);
  2076. this.init(this.chart);
  2077. };
  2078. /**
  2079. * Render the navigator
  2080. *
  2081. * @private
  2082. * @function Highcharts.Navigator#render
  2083. * @param {number} min
  2084. * X axis value minimum
  2085. * @param {number} max
  2086. * X axis value maximum
  2087. * @param {number} [pxMin]
  2088. * Pixel value minimum
  2089. * @param {number} [pxMax]
  2090. * Pixel value maximum
  2091. * @return {void}
  2092. */
  2093. Navigator.prototype.render = function (min, max, pxMin, pxMax) {
  2094. var navigator = this,
  2095. chart = navigator.chart,
  2096. navigatorWidth,
  2097. scrollbarLeft,
  2098. scrollbarTop,
  2099. scrollbarHeight = navigator.scrollbarHeight,
  2100. navigatorSize,
  2101. xAxis = navigator.xAxis,
  2102. pointRange = xAxis.pointRange || 0,
  2103. scrollbarXAxis = xAxis.navigatorAxis.fake ? chart.xAxis[0] : xAxis,
  2104. navigatorEnabled = navigator.navigatorEnabled,
  2105. zoomedMin,
  2106. zoomedMax,
  2107. rendered = navigator.rendered,
  2108. inverted = chart.inverted,
  2109. verb,
  2110. newMin,
  2111. newMax,
  2112. currentRange,
  2113. minRange = chart.xAxis[0].minRange,
  2114. maxRange = chart.xAxis[0].options.maxRange;
  2115. // Don't redraw while moving the handles (#4703).
  2116. if (this.hasDragged && !defined(pxMin)) {
  2117. return;
  2118. }
  2119. min = correctFloat(min - pointRange / 2);
  2120. max = correctFloat(max + pointRange / 2);
  2121. // Don't render the navigator until we have data (#486, #4202, #5172).
  2122. if (!isNumber(min) || !isNumber(max)) {
  2123. // However, if navigator was already rendered, we may need to resize
  2124. // it. For example hidden series, but visible navigator (#6022).
  2125. if (rendered) {
  2126. pxMin = 0;
  2127. pxMax = pick(xAxis.width, scrollbarXAxis.width);
  2128. }
  2129. else {
  2130. return;
  2131. }
  2132. }
  2133. navigator.left = pick(xAxis.left,
  2134. // in case of scrollbar only, without navigator
  2135. chart.plotLeft + scrollbarHeight +
  2136. (inverted ? chart.plotWidth : 0));
  2137. navigator.size = zoomedMax = navigatorSize = pick(xAxis.len, (inverted ? chart.plotHeight : chart.plotWidth) -
  2138. 2 * scrollbarHeight);
  2139. if (inverted) {
  2140. navigatorWidth = scrollbarHeight;
  2141. }
  2142. else {
  2143. navigatorWidth = navigatorSize + 2 * scrollbarHeight;
  2144. }
  2145. // Get the pixel position of the handles
  2146. pxMin = pick(pxMin, xAxis.toPixels(min, true));
  2147. pxMax = pick(pxMax, xAxis.toPixels(max, true));
  2148. // Verify (#1851, #2238)
  2149. if (!isNumber(pxMin) || Math.abs(pxMin) === Infinity) {
  2150. pxMin = 0;
  2151. pxMax = navigatorWidth;
  2152. }
  2153. // Are we below the minRange? (#2618, #6191)
  2154. newMin = xAxis.toValue(pxMin, true);
  2155. newMax = xAxis.toValue(pxMax, true);
  2156. currentRange = Math.abs(correctFloat(newMax - newMin));
  2157. if (currentRange < minRange) {
  2158. if (this.grabbedLeft) {
  2159. pxMin = xAxis.toPixels(newMax - minRange - pointRange, true);
  2160. }
  2161. else if (this.grabbedRight) {
  2162. pxMax = xAxis.toPixels(newMin + minRange + pointRange, true);
  2163. }
  2164. }
  2165. else if (defined(maxRange) &&
  2166. correctFloat(currentRange - pointRange) > maxRange) {
  2167. if (this.grabbedLeft) {
  2168. pxMin = xAxis.toPixels(newMax - maxRange - pointRange, true);
  2169. }
  2170. else if (this.grabbedRight) {
  2171. pxMax = xAxis.toPixels(newMin + maxRange + pointRange, true);
  2172. }
  2173. }
  2174. // Handles are allowed to cross, but never exceed the plot area
  2175. navigator.zoomedMax = clamp(Math.max(pxMin, pxMax), 0, zoomedMax);
  2176. navigator.zoomedMin = clamp(navigator.fixedWidth ?
  2177. navigator.zoomedMax - navigator.fixedWidth :
  2178. Math.min(pxMin, pxMax), 0, zoomedMax);
  2179. navigator.range = navigator.zoomedMax - navigator.zoomedMin;
  2180. zoomedMax = Math.round(navigator.zoomedMax);
  2181. zoomedMin = Math.round(navigator.zoomedMin);
  2182. if (navigatorEnabled) {
  2183. navigator.navigatorGroup.attr({
  2184. visibility: 'visible'
  2185. });
  2186. // Place elements
  2187. verb = rendered && !navigator.hasDragged ? 'animate' : 'attr';
  2188. navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
  2189. navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
  2190. if (navigator.navigatorOptions.handles.enabled) {
  2191. navigator.drawHandle(zoomedMin, 0, inverted, verb);
  2192. navigator.drawHandle(zoomedMax, 1, inverted, verb);
  2193. }
  2194. }
  2195. if (navigator.scrollbar) {
  2196. if (inverted) {
  2197. scrollbarTop = navigator.top - scrollbarHeight;
  2198. scrollbarLeft = navigator.left - scrollbarHeight +
  2199. (navigatorEnabled || !scrollbarXAxis.opposite ? 0 :
  2200. // Multiple axes has offsets:
  2201. (scrollbarXAxis.titleOffset || 0) +
  2202. // Self margin from the axis.title
  2203. scrollbarXAxis.axisTitleMargin);
  2204. scrollbarHeight = navigatorSize + 2 * scrollbarHeight;
  2205. }
  2206. else {
  2207. scrollbarTop = navigator.top + (navigatorEnabled ?
  2208. navigator.height :
  2209. -scrollbarHeight);
  2210. scrollbarLeft = navigator.left - scrollbarHeight;
  2211. }
  2212. // Reposition scrollbar
  2213. navigator.scrollbar.position(scrollbarLeft, scrollbarTop, navigatorWidth, scrollbarHeight);
  2214. // Keep scale 0-1
  2215. navigator.scrollbar.setRange(
  2216. // Use real value, not rounded because range can be very small
  2217. // (#1716)
  2218. navigator.zoomedMin / (navigatorSize || 1), navigator.zoomedMax / (navigatorSize || 1));
  2219. }
  2220. navigator.rendered = true;
  2221. };
  2222. /**
  2223. * Set up the mouse and touch events for the navigator
  2224. *
  2225. * @private
  2226. * @function Highcharts.Navigator#addMouseEvents
  2227. */
  2228. Navigator.prototype.addMouseEvents = function () {
  2229. var navigator = this,
  2230. chart = navigator.chart,
  2231. container = chart.container,
  2232. eventsToUnbind = [],
  2233. mouseMoveHandler,
  2234. mouseUpHandler;
  2235. /**
  2236. * Create mouse events' handlers.
  2237. * Make them as separate functions to enable wrapping them:
  2238. */
  2239. navigator.mouseMoveHandler = mouseMoveHandler = function (e) {
  2240. navigator.onMouseMove(e);
  2241. };
  2242. navigator.mouseUpHandler = mouseUpHandler = function (e) {
  2243. navigator.onMouseUp(e);
  2244. };
  2245. // Add shades and handles mousedown events
  2246. eventsToUnbind = navigator.getPartsEvents('mousedown');
  2247. // Add mouse move and mouseup events. These are bind to doc/container,
  2248. // because Navigator.grabbedSomething flags are stored in mousedown
  2249. // events
  2250. eventsToUnbind.push(addEvent(chart.renderTo, 'mousemove', mouseMoveHandler), addEvent(container.ownerDocument, 'mouseup', mouseUpHandler));
  2251. // Touch events
  2252. if (hasTouch) {
  2253. eventsToUnbind.push(addEvent(chart.renderTo, 'touchmove', mouseMoveHandler), addEvent(container.ownerDocument, 'touchend', mouseUpHandler));
  2254. eventsToUnbind.concat(navigator.getPartsEvents('touchstart'));
  2255. }
  2256. navigator.eventsToUnbind = eventsToUnbind;
  2257. // Data events
  2258. if (navigator.series && navigator.series[0]) {
  2259. eventsToUnbind.push(addEvent(navigator.series[0].xAxis, 'foundExtremes', function () {
  2260. chart.navigator.modifyNavigatorAxisExtremes();
  2261. }));
  2262. }
  2263. };
  2264. /**
  2265. * Generate events for handles and masks
  2266. *
  2267. * @private
  2268. * @function Highcharts.Navigator#getPartsEvents
  2269. *
  2270. * @param {string} eventName
  2271. * Event name handler, 'mousedown' or 'touchstart'
  2272. *
  2273. * @return {Array<Function>}
  2274. * An array of functions to remove navigator functions from the
  2275. * events again.
  2276. */
  2277. Navigator.prototype.getPartsEvents = function (eventName) {
  2278. var navigator = this,
  2279. events = [];
  2280. ['shades', 'handles'].forEach(function (name) {
  2281. navigator[name].forEach(function (navigatorItem, index) {
  2282. events.push(addEvent(navigatorItem.element, eventName, function (e) {
  2283. navigator[name + 'Mousedown'](e, index);
  2284. }));
  2285. });
  2286. });
  2287. return events;
  2288. };
  2289. /**
  2290. * Mousedown on a shaded mask, either:
  2291. *
  2292. * - will be stored for future drag&drop
  2293. *
  2294. * - will directly shift to a new range
  2295. *
  2296. * @private
  2297. * @function Highcharts.Navigator#shadesMousedown
  2298. *
  2299. * @param {Highcharts.PointerEventObject} e
  2300. * Mouse event
  2301. *
  2302. * @param {number} index
  2303. * Index of a mask in Navigator.shades array
  2304. */
  2305. Navigator.prototype.shadesMousedown = function (e, index) {
  2306. e = this.chart.pointer.normalize(e);
  2307. var navigator = this,
  2308. chart = navigator.chart,
  2309. xAxis = navigator.xAxis,
  2310. zoomedMin = navigator.zoomedMin,
  2311. navigatorPosition = navigator.left,
  2312. navigatorSize = navigator.size,
  2313. range = navigator.range,
  2314. chartX = e.chartX,
  2315. fixedMax,
  2316. fixedMin,
  2317. ext,
  2318. left;
  2319. // For inverted chart, swap some options:
  2320. if (chart.inverted) {
  2321. chartX = e.chartY;
  2322. navigatorPosition = navigator.top;
  2323. }
  2324. if (index === 1) {
  2325. // Store information for drag&drop
  2326. navigator.grabbedCenter = chartX;
  2327. navigator.fixedWidth = range;
  2328. navigator.dragOffset = chartX - zoomedMin;
  2329. }
  2330. else {
  2331. // Shift the range by clicking on shaded areas
  2332. left = chartX - navigatorPosition - range / 2;
  2333. if (index === 0) {
  2334. left = Math.max(0, left);
  2335. }
  2336. else if (index === 2 && left + range >= navigatorSize) {
  2337. left = navigatorSize - range;
  2338. if (navigator.reversedExtremes) {
  2339. // #7713
  2340. left -= range;
  2341. fixedMin = navigator.getUnionExtremes().dataMin;
  2342. }
  2343. else {
  2344. // #2293, #3543
  2345. fixedMax = navigator.getUnionExtremes().dataMax;
  2346. }
  2347. }
  2348. if (left !== zoomedMin) { // it has actually moved
  2349. navigator.fixedWidth = range; // #1370
  2350. ext = xAxis.navigatorAxis.toFixedRange(left, left + range, fixedMin, fixedMax);
  2351. if (defined(ext.min)) { // #7411
  2352. chart.xAxis[0].setExtremes(Math.min(ext.min, ext.max), Math.max(ext.min, ext.max), true, null, // auto animation
  2353. { trigger: 'navigator' });
  2354. }
  2355. }
  2356. }
  2357. };
  2358. /**
  2359. * Mousedown on a handle mask.
  2360. * Will store necessary information for drag&drop.
  2361. *
  2362. * @private
  2363. * @function Highcharts.Navigator#handlesMousedown
  2364. * @param {Highcharts.PointerEventObject} e
  2365. * Mouse event
  2366. * @param {number} index
  2367. * Index of a handle in Navigator.handles array
  2368. * @return {void}
  2369. */
  2370. Navigator.prototype.handlesMousedown = function (e, index) {
  2371. e = this.chart.pointer.normalize(e);
  2372. var navigator = this,
  2373. chart = navigator.chart,
  2374. baseXAxis = chart.xAxis[0],
  2375. // For reversed axes, min and max are changed,
  2376. // so the other extreme should be stored
  2377. reverse = navigator.reversedExtremes;
  2378. if (index === 0) {
  2379. // Grab the left handle
  2380. navigator.grabbedLeft = true;
  2381. navigator.otherHandlePos = navigator.zoomedMax;
  2382. navigator.fixedExtreme = reverse ? baseXAxis.min : baseXAxis.max;
  2383. }
  2384. else {
  2385. // Grab the right handle
  2386. navigator.grabbedRight = true;
  2387. navigator.otherHandlePos = navigator.zoomedMin;
  2388. navigator.fixedExtreme = reverse ? baseXAxis.max : baseXAxis.min;
  2389. }
  2390. chart.fixedRange = null;
  2391. };
  2392. /**
  2393. * Mouse move event based on x/y mouse position.
  2394. *
  2395. * @private
  2396. * @function Highcharts.Navigator#onMouseMove
  2397. *
  2398. * @param {Highcharts.PointerEventObject} e
  2399. * Mouse event
  2400. */
  2401. Navigator.prototype.onMouseMove = function (e) {
  2402. var navigator = this,
  2403. chart = navigator.chart,
  2404. left = navigator.left,
  2405. navigatorSize = navigator.navigatorSize,
  2406. range = navigator.range,
  2407. dragOffset = navigator.dragOffset,
  2408. inverted = chart.inverted,
  2409. chartX;
  2410. // In iOS, a mousemove event with e.pageX === 0 is fired when holding
  2411. // the finger down in the center of the scrollbar. This should be
  2412. // ignored.
  2413. if (!e.touches || e.touches[0].pageX !== 0) { // #4696
  2414. e = chart.pointer.normalize(e);
  2415. chartX = e.chartX;
  2416. // Swap some options for inverted chart
  2417. if (inverted) {
  2418. left = navigator.top;
  2419. chartX = e.chartY;
  2420. }
  2421. // Drag left handle or top handle
  2422. if (navigator.grabbedLeft) {
  2423. navigator.hasDragged = true;
  2424. navigator.render(0, 0, chartX - left, navigator.otherHandlePos);
  2425. // Drag right handle or bottom handle
  2426. }
  2427. else if (navigator.grabbedRight) {
  2428. navigator.hasDragged = true;
  2429. navigator.render(0, 0, navigator.otherHandlePos, chartX - left);
  2430. // Drag scrollbar or open area in navigator
  2431. }
  2432. else if (navigator.grabbedCenter) {
  2433. navigator.hasDragged = true;
  2434. if (chartX < dragOffset) { // outside left
  2435. chartX = dragOffset;
  2436. // outside right
  2437. }
  2438. else if (chartX >
  2439. navigatorSize + dragOffset - range) {
  2440. chartX = navigatorSize + dragOffset - range;
  2441. }
  2442. navigator.render(0, 0, chartX - dragOffset, chartX - dragOffset + range);
  2443. }
  2444. if (navigator.hasDragged &&
  2445. navigator.scrollbar &&
  2446. pick(navigator.scrollbar.options.liveRedraw,
  2447. // By default, don't run live redraw on VML, on touch
  2448. // devices or if the chart is in boost.
  2449. H.svg && !isTouchDevice && !this.chart.isBoosting)) {
  2450. e.DOMType = e.type; // DOMType is for IE8
  2451. setTimeout(function () {
  2452. navigator.onMouseUp(e);
  2453. }, 0);
  2454. }
  2455. }
  2456. };
  2457. /**
  2458. * Mouse up event based on x/y mouse position.
  2459. *
  2460. * @private
  2461. * @function Highcharts.Navigator#onMouseUp
  2462. * @param {Highcharts.PointerEventObject} e
  2463. * Mouse event
  2464. * @return {void}
  2465. */
  2466. Navigator.prototype.onMouseUp = function (e) {
  2467. var navigator = this,
  2468. chart = navigator.chart,
  2469. xAxis = navigator.xAxis,
  2470. scrollbar = navigator.scrollbar,
  2471. DOMEvent = e.DOMEvent || e,
  2472. inverted = chart.inverted,
  2473. verb = navigator.rendered && !navigator.hasDragged ?
  2474. 'animate' : 'attr',
  2475. zoomedMax,
  2476. zoomedMin,
  2477. unionExtremes,
  2478. fixedMin,
  2479. fixedMax,
  2480. ext;
  2481. if (
  2482. // MouseUp is called for both, navigator and scrollbar (that order),
  2483. // which causes calling afterSetExtremes twice. Prevent first call
  2484. // by checking if scrollbar is going to set new extremes (#6334)
  2485. (navigator.hasDragged && (!scrollbar || !scrollbar.hasDragged)) ||
  2486. e.trigger === 'scrollbar') {
  2487. unionExtremes = navigator.getUnionExtremes();
  2488. // When dragging one handle, make sure the other one doesn't change
  2489. if (navigator.zoomedMin === navigator.otherHandlePos) {
  2490. fixedMin = navigator.fixedExtreme;
  2491. }
  2492. else if (navigator.zoomedMax === navigator.otherHandlePos) {
  2493. fixedMax = navigator.fixedExtreme;
  2494. }
  2495. // Snap to right edge (#4076)
  2496. if (navigator.zoomedMax === navigator.size) {
  2497. fixedMax = navigator.reversedExtremes ?
  2498. unionExtremes.dataMin :
  2499. unionExtremes.dataMax;
  2500. }
  2501. // Snap to left edge (#7576)
  2502. if (navigator.zoomedMin === 0) {
  2503. fixedMin = navigator.reversedExtremes ?
  2504. unionExtremes.dataMax :
  2505. unionExtremes.dataMin;
  2506. }
  2507. ext = xAxis.navigatorAxis.toFixedRange(navigator.zoomedMin, navigator.zoomedMax, fixedMin, fixedMax);
  2508. if (defined(ext.min)) {
  2509. chart.xAxis[0].setExtremes(Math.min(ext.min, ext.max), Math.max(ext.min, ext.max), true,
  2510. // Run animation when clicking buttons, scrollbar track etc,
  2511. // but not when dragging handles or scrollbar
  2512. navigator.hasDragged ? false : null, {
  2513. trigger: 'navigator',
  2514. triggerOp: 'navigator-drag',
  2515. DOMEvent: DOMEvent // #1838
  2516. });
  2517. }
  2518. }
  2519. if (e.DOMType !== 'mousemove' &&
  2520. e.DOMType !== 'touchmove') {
  2521. navigator.grabbedLeft = navigator.grabbedRight =
  2522. navigator.grabbedCenter = navigator.fixedWidth =
  2523. navigator.fixedExtreme = navigator.otherHandlePos =
  2524. navigator.hasDragged = navigator.dragOffset = null;
  2525. }
  2526. // Update position of navigator shades, outline and handles (#12573)
  2527. if (navigator.navigatorEnabled &&
  2528. isNumber(navigator.zoomedMin) &&
  2529. isNumber(navigator.zoomedMax)) {
  2530. zoomedMin = Math.round(navigator.zoomedMin);
  2531. zoomedMax = Math.round(navigator.zoomedMax);
  2532. if (navigator.shades) {
  2533. navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
  2534. }
  2535. if (navigator.outline) {
  2536. navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
  2537. }
  2538. if (navigator.navigatorOptions.handles.enabled &&
  2539. Object.keys(navigator.handles).length ===
  2540. navigator.handles.length) {
  2541. navigator.drawHandle(zoomedMin, 0, inverted, verb);
  2542. navigator.drawHandle(zoomedMax, 1, inverted, verb);
  2543. }
  2544. }
  2545. };
  2546. /**
  2547. * Removes the event handlers attached previously with addEvents.
  2548. *
  2549. * @private
  2550. * @function Highcharts.Navigator#removeEvents
  2551. * @return {void}
  2552. */
  2553. Navigator.prototype.removeEvents = function () {
  2554. if (this.eventsToUnbind) {
  2555. this.eventsToUnbind.forEach(function (unbind) {
  2556. unbind();
  2557. });
  2558. this.eventsToUnbind = void 0;
  2559. }
  2560. this.removeBaseSeriesEvents();
  2561. };
  2562. /**
  2563. * Remove data events.
  2564. *
  2565. * @private
  2566. * @function Highcharts.Navigator#removeBaseSeriesEvents
  2567. * @return {void}
  2568. */
  2569. Navigator.prototype.removeBaseSeriesEvents = function () {
  2570. var baseSeries = this.baseSeries || [];
  2571. if (this.navigatorEnabled && baseSeries[0]) {
  2572. if (this.navigatorOptions.adaptToUpdatedData !== false) {
  2573. baseSeries.forEach(function (series) {
  2574. removeEvent(series, 'updatedData', this.updatedDataHandler);
  2575. }, this);
  2576. }
  2577. // We only listen for extremes-events on the first baseSeries
  2578. if (baseSeries[0].xAxis) {
  2579. removeEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes);
  2580. }
  2581. }
  2582. };
  2583. /**
  2584. * Initialize the Navigator object
  2585. *
  2586. * @private
  2587. * @function Highcharts.Navigator#init
  2588. *
  2589. * @param {Highcharts.Chart} chart
  2590. */
  2591. Navigator.prototype.init = function (chart) {
  2592. var chartOptions = chart.options,
  2593. navigatorOptions = chartOptions.navigator,
  2594. navigatorEnabled = navigatorOptions.enabled,
  2595. scrollbarOptions = chartOptions.scrollbar,
  2596. scrollbarEnabled = scrollbarOptions.enabled,
  2597. height = navigatorEnabled ? navigatorOptions.height : 0,
  2598. scrollbarHeight = scrollbarEnabled ?
  2599. scrollbarOptions.height :
  2600. 0;
  2601. this.handles = [];
  2602. this.shades = [];
  2603. this.chart = chart;
  2604. this.setBaseSeries();
  2605. this.height = height;
  2606. this.scrollbarHeight = scrollbarHeight;
  2607. this.scrollbarEnabled = scrollbarEnabled;
  2608. this.navigatorEnabled = navigatorEnabled;
  2609. this.navigatorOptions = navigatorOptions;
  2610. this.scrollbarOptions = scrollbarOptions;
  2611. this.outlineHeight = height + scrollbarHeight;
  2612. this.opposite = pick(navigatorOptions.opposite, Boolean(!navigatorEnabled && chart.inverted)); // #6262
  2613. var navigator = this,
  2614. baseSeries = navigator.baseSeries,
  2615. xAxisIndex = chart.xAxis.length,
  2616. yAxisIndex = chart.yAxis.length,
  2617. baseXaxis = baseSeries && baseSeries[0] && baseSeries[0].xAxis ||
  2618. chart.xAxis[0] || { options: {} };
  2619. chart.isDirtyBox = true;
  2620. if (navigator.navigatorEnabled) {
  2621. // an x axis is required for scrollbar also
  2622. navigator.xAxis = new Axis(chart, merge({
  2623. // inherit base xAxis' break and ordinal options
  2624. breaks: baseXaxis.options.breaks,
  2625. ordinal: baseXaxis.options.ordinal
  2626. }, navigatorOptions.xAxis, {
  2627. id: 'navigator-x-axis',
  2628. yAxis: 'navigator-y-axis',
  2629. isX: true,
  2630. type: 'datetime',
  2631. index: xAxisIndex,
  2632. isInternal: true,
  2633. offset: 0,
  2634. keepOrdinalPadding: true,
  2635. startOnTick: false,
  2636. endOnTick: false,
  2637. minPadding: 0,
  2638. maxPadding: 0,
  2639. zoomEnabled: false
  2640. }, chart.inverted ? {
  2641. offsets: [scrollbarHeight, 0, -scrollbarHeight, 0],
  2642. width: height
  2643. } : {
  2644. offsets: [0, -scrollbarHeight, 0, scrollbarHeight],
  2645. height: height
  2646. }));
  2647. navigator.yAxis = new Axis(chart, merge(navigatorOptions.yAxis, {
  2648. id: 'navigator-y-axis',
  2649. alignTicks: false,
  2650. offset: 0,
  2651. index: yAxisIndex,
  2652. isInternal: true,
  2653. zoomEnabled: false
  2654. }, chart.inverted ? {
  2655. width: height
  2656. } : {
  2657. height: height
  2658. }));
  2659. // If we have a base series, initialize the navigator series
  2660. if (baseSeries || navigatorOptions.series.data) {
  2661. navigator.updateNavigatorSeries(false);
  2662. // If not, set up an event to listen for added series
  2663. }
  2664. else if (chart.series.length === 0) {
  2665. navigator.unbindRedraw = addEvent(chart, 'beforeRedraw', function () {
  2666. // We've got one, now add it as base
  2667. if (chart.series.length > 0 && !navigator.series) {
  2668. navigator.setBaseSeries();
  2669. navigator.unbindRedraw(); // reset
  2670. }
  2671. });
  2672. }
  2673. navigator.reversedExtremes = (chart.inverted && !navigator.xAxis.reversed) || (!chart.inverted && navigator.xAxis.reversed);
  2674. // Render items, so we can bind events to them:
  2675. navigator.renderElements();
  2676. // Add mouse events
  2677. navigator.addMouseEvents();
  2678. // in case of scrollbar only, fake an x axis to get translation
  2679. }
  2680. else {
  2681. navigator.xAxis = {
  2682. chart: chart,
  2683. navigatorAxis: {
  2684. fake: true
  2685. },
  2686. translate: function (value, reverse) {
  2687. var axis = chart.xAxis[0], ext = axis.getExtremes(), scrollTrackWidth = axis.len - 2 * scrollbarHeight, min = numExt('min', axis.options.min, ext.dataMin), valueRange = numExt('max', axis.options.max, ext.dataMax) - min;
  2688. return reverse ?
  2689. // from pixel to value
  2690. (value * valueRange / scrollTrackWidth) + min :
  2691. // from value to pixel
  2692. scrollTrackWidth * (value - min) / valueRange;
  2693. },
  2694. toPixels: function (value) {
  2695. return this.translate(value);
  2696. },
  2697. toValue: function (value) {
  2698. return this.translate(value, true);
  2699. }
  2700. };
  2701. navigator.xAxis.navigatorAxis.axis = navigator.xAxis;
  2702. navigator.xAxis.navigatorAxis.toFixedRange = (NavigatorAxis.AdditionsClass.prototype.toFixedRange.bind(navigator.xAxis.navigatorAxis));
  2703. }
  2704. // Initialize the scrollbar
  2705. if (chart.options.scrollbar.enabled) {
  2706. chart.scrollbar = navigator.scrollbar = new Scrollbar(chart.renderer, merge(chart.options.scrollbar, {
  2707. margin: navigator.navigatorEnabled ? 0 : 10,
  2708. vertical: chart.inverted
  2709. }), chart);
  2710. addEvent(navigator.scrollbar, 'changed', function (e) {
  2711. var range = navigator.size,
  2712. to = range * this.to,
  2713. from = range * this.from;
  2714. navigator.hasDragged = navigator.scrollbar.hasDragged;
  2715. navigator.render(0, 0, from, to);
  2716. if (chart.options.scrollbar.liveRedraw ||
  2717. (e.DOMType !== 'mousemove' &&
  2718. e.DOMType !== 'touchmove')) {
  2719. setTimeout(function () {
  2720. navigator.onMouseUp(e);
  2721. });
  2722. }
  2723. });
  2724. }
  2725. // Add data events
  2726. navigator.addBaseSeriesEvents();
  2727. // Add redraw events
  2728. navigator.addChartEvents();
  2729. };
  2730. /**
  2731. * Get the union data extremes of the chart - the outer data extremes of the
  2732. * base X axis and the navigator axis.
  2733. *
  2734. * @private
  2735. * @function Highcharts.Navigator#getUnionExtremes
  2736. * @param {boolean} [returnFalseOnNoBaseSeries]
  2737. * as the param says.
  2738. * @return {Highcharts.Dictionary<(number|undefined)>|undefined}
  2739. */
  2740. Navigator.prototype.getUnionExtremes = function (returnFalseOnNoBaseSeries) {
  2741. var baseAxis = this.chart.xAxis[0],
  2742. navAxis = this.xAxis,
  2743. navAxisOptions = navAxis.options,
  2744. baseAxisOptions = baseAxis.options,
  2745. ret;
  2746. if (!returnFalseOnNoBaseSeries || baseAxis.dataMin !== null) {
  2747. ret = {
  2748. dataMin: pick(// #4053
  2749. navAxisOptions && navAxisOptions.min, numExt('min', baseAxisOptions.min, baseAxis.dataMin, navAxis.dataMin, navAxis.min)),
  2750. dataMax: pick(navAxisOptions && navAxisOptions.max, numExt('max', baseAxisOptions.max, baseAxis.dataMax, navAxis.dataMax, navAxis.max))
  2751. };
  2752. }
  2753. return ret;
  2754. };
  2755. /**
  2756. * Set the base series and update the navigator series from this. With a bit
  2757. * of modification we should be able to make this an API method to be called
  2758. * from the outside
  2759. *
  2760. * @private
  2761. * @function Highcharts.Navigator#setBaseSeries
  2762. * @param {Highcharts.SeriesOptionsType} [baseSeriesOptions]
  2763. * Additional series options for a navigator
  2764. * @param {boolean} [redraw]
  2765. * Whether to redraw after update.
  2766. * @return {void}
  2767. */
  2768. Navigator.prototype.setBaseSeries = function (baseSeriesOptions, redraw) {
  2769. var chart = this.chart,
  2770. baseSeries = this.baseSeries = [];
  2771. baseSeriesOptions = (baseSeriesOptions ||
  2772. chart.options && chart.options.navigator.baseSeries ||
  2773. (chart.series.length ?
  2774. // Find the first non-navigator series (#8430)
  2775. find(chart.series, function (s) {
  2776. return !s.options.isInternal;
  2777. }).index :
  2778. 0));
  2779. // Iterate through series and add the ones that should be shown in
  2780. // navigator.
  2781. (chart.series || []).forEach(function (series, i) {
  2782. if (
  2783. // Don't include existing nav series
  2784. !series.options.isInternal &&
  2785. (series.options.showInNavigator ||
  2786. (i === baseSeriesOptions ||
  2787. series.options.id === baseSeriesOptions) &&
  2788. series.options.showInNavigator !== false)) {
  2789. baseSeries.push(series);
  2790. }
  2791. });
  2792. // When run after render, this.xAxis already exists
  2793. if (this.xAxis && !this.xAxis.navigatorAxis.fake) {
  2794. this.updateNavigatorSeries(true, redraw);
  2795. }
  2796. };
  2797. /**
  2798. * Update series in the navigator from baseSeries, adding new if does not
  2799. * exist.
  2800. *
  2801. * @private
  2802. * @function Highcharts.Navigator.updateNavigatorSeries
  2803. * @param {boolean} addEvents
  2804. * @param {boolean} [redraw]
  2805. * @return {void}
  2806. */
  2807. Navigator.prototype.updateNavigatorSeries = function (addEvents, redraw) {
  2808. var navigator = this,
  2809. chart = navigator.chart,
  2810. baseSeries = navigator.baseSeries,
  2811. baseOptions,
  2812. mergedNavSeriesOptions,
  2813. chartNavigatorSeriesOptions = navigator.navigatorOptions.series,
  2814. baseNavigatorOptions,
  2815. navSeriesMixin = {
  2816. enableMouseTracking: false,
  2817. index: null,
  2818. linkedTo: null,
  2819. group: 'nav',
  2820. padXAxis: false,
  2821. xAxis: 'navigator-x-axis',
  2822. yAxis: 'navigator-y-axis',
  2823. showInLegend: false,
  2824. stacking: void 0,
  2825. isInternal: true,
  2826. states: {
  2827. inactive: {
  2828. opacity: 1
  2829. }
  2830. }
  2831. },
  2832. // Remove navigator series that are no longer in the baseSeries
  2833. navigatorSeries = navigator.series =
  2834. (navigator.series || []).filter(function (navSeries) {
  2835. var base = navSeries.baseSeries;
  2836. if (baseSeries.indexOf(base) < 0) { // Not in array
  2837. // If there is still a base series connected to this
  2838. // series, remove event handler and reference.
  2839. if (base) {
  2840. removeEvent(base, 'updatedData', navigator.updatedDataHandler);
  2841. delete base.navigatorSeries;
  2842. }
  2843. // Kill the nav series. It may already have been
  2844. // destroyed (#8715).
  2845. if (navSeries.chart) {
  2846. navSeries.destroy();
  2847. }
  2848. return false;
  2849. }
  2850. return true;
  2851. });
  2852. // Go through each base series and merge the options to create new
  2853. // series
  2854. if (baseSeries && baseSeries.length) {
  2855. baseSeries.forEach(function eachBaseSeries(base) {
  2856. var linkedNavSeries = base.navigatorSeries,
  2857. userNavOptions = extend(
  2858. // Grab color and visibility from base as default
  2859. {
  2860. color: base.color,
  2861. visible: base.visible
  2862. }, !isArray(chartNavigatorSeriesOptions) ?
  2863. chartNavigatorSeriesOptions :
  2864. defaultOptions.navigator.series);
  2865. // Don't update if the series exists in nav and we have disabled
  2866. // adaptToUpdatedData.
  2867. if (linkedNavSeries &&
  2868. navigator.navigatorOptions.adaptToUpdatedData === false) {
  2869. return;
  2870. }
  2871. navSeriesMixin.name = 'Navigator ' + baseSeries.length;
  2872. baseOptions = base.options || {};
  2873. baseNavigatorOptions = baseOptions.navigatorOptions || {};
  2874. mergedNavSeriesOptions = merge(baseOptions, navSeriesMixin, userNavOptions, baseNavigatorOptions);
  2875. // Once nav series type is resolved, pick correct pointRange
  2876. mergedNavSeriesOptions.pointRange = pick(
  2877. // Stricte set pointRange in options
  2878. userNavOptions.pointRange, baseNavigatorOptions.pointRange,
  2879. // Fallback to default values, e.g. `null` for column
  2880. defaultOptions.plotOptions[mergedNavSeriesOptions.type || 'line'].pointRange);
  2881. // Merge data separately. Do a slice to avoid mutating the
  2882. // navigator options from base series (#4923).
  2883. var navigatorSeriesData = baseNavigatorOptions.data || userNavOptions.data;
  2884. navigator.hasNavigatorData =
  2885. navigator.hasNavigatorData || !!navigatorSeriesData;
  2886. mergedNavSeriesOptions.data =
  2887. navigatorSeriesData ||
  2888. baseOptions.data && baseOptions.data.slice(0);
  2889. // Update or add the series
  2890. if (linkedNavSeries && linkedNavSeries.options) {
  2891. linkedNavSeries.update(mergedNavSeriesOptions, redraw);
  2892. }
  2893. else {
  2894. base.navigatorSeries = chart.initSeries(mergedNavSeriesOptions);
  2895. base.navigatorSeries.baseSeries = base; // Store ref
  2896. navigatorSeries.push(base.navigatorSeries);
  2897. }
  2898. });
  2899. }
  2900. // If user has defined data (and no base series) or explicitly defined
  2901. // navigator.series as an array, we create these series on top of any
  2902. // base series.
  2903. if (chartNavigatorSeriesOptions.data &&
  2904. !(baseSeries && baseSeries.length) ||
  2905. isArray(chartNavigatorSeriesOptions)) {
  2906. navigator.hasNavigatorData = false;
  2907. // Allow navigator.series to be an array
  2908. chartNavigatorSeriesOptions =
  2909. splat(chartNavigatorSeriesOptions);
  2910. chartNavigatorSeriesOptions.forEach(function (userSeriesOptions, i) {
  2911. navSeriesMixin.name =
  2912. 'Navigator ' + (navigatorSeries.length + 1);
  2913. mergedNavSeriesOptions = merge(defaultOptions.navigator.series, {
  2914. // Since we don't have a base series to pull color from,
  2915. // try to fake it by using color from series with same
  2916. // index. Otherwise pull from the colors array. We need
  2917. // an explicit color as otherwise updates will increment
  2918. // color counter and we'll get a new color for each
  2919. // update of the nav series.
  2920. color: chart.series[i] &&
  2921. !chart.series[i].options.isInternal &&
  2922. chart.series[i].color ||
  2923. chart.options.colors[i] ||
  2924. chart.options.colors[0]
  2925. }, navSeriesMixin, userSeriesOptions);
  2926. mergedNavSeriesOptions.data = userSeriesOptions.data;
  2927. if (mergedNavSeriesOptions.data) {
  2928. navigator.hasNavigatorData = true;
  2929. navigatorSeries.push(chart.initSeries(mergedNavSeriesOptions));
  2930. }
  2931. });
  2932. }
  2933. if (addEvents) {
  2934. this.addBaseSeriesEvents();
  2935. }
  2936. };
  2937. /**
  2938. * Add data events.
  2939. * For example when main series is updated we need to recalculate extremes
  2940. *
  2941. * @private
  2942. * @function Highcharts.Navigator#addBaseSeriesEvent
  2943. * @return {void}
  2944. */
  2945. Navigator.prototype.addBaseSeriesEvents = function () {
  2946. var navigator = this,
  2947. baseSeries = navigator.baseSeries || [];
  2948. // Bind modified extremes event to first base's xAxis only.
  2949. // In event of > 1 base-xAxes, the navigator will ignore those.
  2950. // Adding this multiple times to the same axis is no problem, as
  2951. // duplicates should be discarded by the browser.
  2952. if (baseSeries[0] && baseSeries[0].xAxis) {
  2953. addEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes);
  2954. }
  2955. baseSeries.forEach(function (base) {
  2956. // Link base series show/hide to navigator series visibility
  2957. addEvent(base, 'show', function () {
  2958. if (this.navigatorSeries) {
  2959. this.navigatorSeries.setVisible(true, false);
  2960. }
  2961. });
  2962. addEvent(base, 'hide', function () {
  2963. if (this.navigatorSeries) {
  2964. this.navigatorSeries.setVisible(false, false);
  2965. }
  2966. });
  2967. // Respond to updated data in the base series, unless explicitily
  2968. // not adapting to data changes.
  2969. if (this.navigatorOptions.adaptToUpdatedData !== false) {
  2970. if (base.xAxis) {
  2971. addEvent(base, 'updatedData', this.updatedDataHandler);
  2972. }
  2973. }
  2974. // Handle series removal
  2975. addEvent(base, 'remove', function () {
  2976. if (this.navigatorSeries) {
  2977. erase(navigator.series, this.navigatorSeries);
  2978. if (defined(this.navigatorSeries.options)) {
  2979. this.navigatorSeries.remove(false);
  2980. }
  2981. delete this.navigatorSeries;
  2982. }
  2983. });
  2984. }, this);
  2985. };
  2986. /**
  2987. * Get minimum from all base series connected to the navigator
  2988. * @private
  2989. * @param {number} currentSeriesMin
  2990. * Minium from the current series
  2991. * @return {number} Minimum from all series
  2992. */
  2993. Navigator.prototype.getBaseSeriesMin = function (currentSeriesMin) {
  2994. return this.baseSeries.reduce(function (min, series) {
  2995. // (#10193)
  2996. return Math.min(min, series.xData ? series.xData[0] : min);
  2997. }, currentSeriesMin);
  2998. };
  2999. /**
  3000. * Set the navigator x axis extremes to reflect the total. The navigator
  3001. * extremes should always be the extremes of the union of all series in the
  3002. * chart as well as the navigator series.
  3003. *
  3004. * @private
  3005. * @function Highcharts.Navigator#modifyNavigatorAxisExtremes
  3006. */
  3007. Navigator.prototype.modifyNavigatorAxisExtremes = function () {
  3008. var xAxis = this.xAxis,
  3009. unionExtremes;
  3010. if (typeof xAxis.getExtremes !== 'undefined') {
  3011. unionExtremes = this.getUnionExtremes(true);
  3012. if (unionExtremes &&
  3013. (unionExtremes.dataMin !== xAxis.min ||
  3014. unionExtremes.dataMax !== xAxis.max)) {
  3015. xAxis.min = unionExtremes.dataMin;
  3016. xAxis.max = unionExtremes.dataMax;
  3017. }
  3018. }
  3019. };
  3020. /**
  3021. * Hook to modify the base axis extremes with information from the Navigator
  3022. *
  3023. * @private
  3024. * @function Highcharts.Navigator#modifyBaseAxisExtremes
  3025. */
  3026. Navigator.prototype.modifyBaseAxisExtremes = function () {
  3027. var baseXAxis = this,
  3028. navigator = baseXAxis.chart.navigator,
  3029. baseExtremes = baseXAxis.getExtremes(),
  3030. baseMin = baseExtremes.min,
  3031. baseMax = baseExtremes.max,
  3032. baseDataMin = baseExtremes.dataMin,
  3033. baseDataMax = baseExtremes.dataMax,
  3034. range = baseMax - baseMin,
  3035. stickToMin = navigator.stickToMin,
  3036. stickToMax = navigator.stickToMax,
  3037. overscroll = pick(baseXAxis.options.overscroll, 0),
  3038. newMax,
  3039. newMin,
  3040. navigatorSeries = navigator.series && navigator.series[0],
  3041. hasSetExtremes = !!baseXAxis.setExtremes,
  3042. // When the extremes have been set by range selector button, don't
  3043. // stick to min or max. The range selector buttons will handle the
  3044. // extremes. (#5489)
  3045. unmutable = baseXAxis.eventArgs &&
  3046. baseXAxis.eventArgs.trigger === 'rangeSelectorButton';
  3047. if (!unmutable) {
  3048. // If the zoomed range is already at the min, move it to the right
  3049. // as new data comes in
  3050. if (stickToMin) {
  3051. newMin = baseDataMin;
  3052. newMax = newMin + range;
  3053. }
  3054. // If the zoomed range is already at the max, move it to the right
  3055. // as new data comes in
  3056. if (stickToMax) {
  3057. newMax = baseDataMax + overscroll;
  3058. // If stickToMin is true, the new min value is set above
  3059. if (!stickToMin) {
  3060. newMin = Math.max(baseDataMin, // don't go below data extremes (#13184)
  3061. newMax - range, navigator.getBaseSeriesMin(navigatorSeries && navigatorSeries.xData ?
  3062. navigatorSeries.xData[0] :
  3063. -Number.MAX_VALUE));
  3064. }
  3065. }
  3066. // Update the extremes
  3067. if (hasSetExtremes && (stickToMin || stickToMax)) {
  3068. if (isNumber(newMin)) {
  3069. baseXAxis.min = baseXAxis.userMin = newMin;
  3070. baseXAxis.max = baseXAxis.userMax = newMax;
  3071. }
  3072. }
  3073. }
  3074. // Reset
  3075. navigator.stickToMin =
  3076. navigator.stickToMax = null;
  3077. };
  3078. /**
  3079. * Handler for updated data on the base series. When data is modified, the
  3080. * navigator series must reflect it. This is called from the Chart.redraw
  3081. * function before axis and series extremes are computed.
  3082. *
  3083. * @private
  3084. * @function Highcharts.Navigator#updateDataHandler
  3085. */
  3086. Navigator.prototype.updatedDataHandler = function () {
  3087. var navigator = this.chart.navigator,
  3088. baseSeries = this,
  3089. navigatorSeries = this.navigatorSeries,
  3090. xDataMin = navigator.getBaseSeriesMin(baseSeries.xData[0]);
  3091. // If the scrollbar is scrolled all the way to the right, keep right as
  3092. // new data comes in.
  3093. navigator.stickToMax = navigator.reversedExtremes ?
  3094. Math.round(navigator.zoomedMin) === 0 :
  3095. Math.round(navigator.zoomedMax) >= Math.round(navigator.size);
  3096. // Detect whether the zoomed area should stick to the minimum or
  3097. // maximum. If the current axis minimum falls outside the new updated
  3098. // dataset, we must adjust.
  3099. navigator.stickToMin = isNumber(baseSeries.xAxis.min) &&
  3100. (baseSeries.xAxis.min <= xDataMin) &&
  3101. (!this.chart.fixedRange || !navigator.stickToMax);
  3102. // Set the navigator series data to the new data of the base series
  3103. if (navigatorSeries && !navigator.hasNavigatorData) {
  3104. navigatorSeries.options.pointStart = baseSeries.xData[0];
  3105. navigatorSeries.setData(baseSeries.options.data, false, null, false); // #5414
  3106. }
  3107. };
  3108. /**
  3109. * Add chart events, like redrawing navigator, when chart requires that.
  3110. *
  3111. * @private
  3112. * @function Highcharts.Navigator#addChartEvents
  3113. * @return {void}
  3114. */
  3115. Navigator.prototype.addChartEvents = function () {
  3116. if (!this.eventsToUnbind) {
  3117. this.eventsToUnbind = [];
  3118. }
  3119. this.eventsToUnbind.push(
  3120. // Move the scrollbar after redraw, like after data updata even if
  3121. // axes don't redraw
  3122. addEvent(this.chart, 'redraw', function () {
  3123. var navigator = this.navigator,
  3124. xAxis = navigator && (navigator.baseSeries &&
  3125. navigator.baseSeries[0] &&
  3126. navigator.baseSeries[0].xAxis ||
  3127. this.xAxis[0]); // #5709, #13114
  3128. if (xAxis) {
  3129. navigator.render(xAxis.min,
  3130. xAxis.max);
  3131. }
  3132. }),
  3133. // Make room for the navigator, can be placed around the chart:
  3134. addEvent(this.chart, 'getMargins', function () {
  3135. var chart = this,
  3136. navigator = chart.navigator,
  3137. marginName = navigator.opposite ?
  3138. 'plotTop' : 'marginBottom';
  3139. if (chart.inverted) {
  3140. marginName = navigator.opposite ?
  3141. 'marginRight' : 'plotLeft';
  3142. }
  3143. chart[marginName] =
  3144. (chart[marginName] || 0) + (navigator.navigatorEnabled || !chart.inverted ?
  3145. navigator.outlineHeight :
  3146. 0) + navigator.navigatorOptions.margin;
  3147. }));
  3148. };
  3149. /**
  3150. * Destroys allocated elements.
  3151. *
  3152. * @private
  3153. * @function Highcharts.Navigator#destroy
  3154. */
  3155. Navigator.prototype.destroy = function () {
  3156. // Disconnect events added in addEvents
  3157. this.removeEvents();
  3158. if (this.xAxis) {
  3159. erase(this.chart.xAxis, this.xAxis);
  3160. erase(this.chart.axes, this.xAxis);
  3161. }
  3162. if (this.yAxis) {
  3163. erase(this.chart.yAxis, this.yAxis);
  3164. erase(this.chart.axes, this.yAxis);
  3165. }
  3166. // Destroy series
  3167. (this.series || []).forEach(function (s) {
  3168. if (s.destroy) {
  3169. s.destroy();
  3170. }
  3171. });
  3172. // Destroy properties
  3173. [
  3174. 'series', 'xAxis', 'yAxis', 'shades', 'outline', 'scrollbarTrack',
  3175. 'scrollbarRifles', 'scrollbarGroup', 'scrollbar', 'navigatorGroup',
  3176. 'rendered'
  3177. ].forEach(function (prop) {
  3178. if (this[prop] && this[prop].destroy) {
  3179. this[prop].destroy();
  3180. }
  3181. this[prop] = null;
  3182. }, this);
  3183. // Destroy elements in collection
  3184. [this.handles].forEach(function (coll) {
  3185. destroyObjectProperties(coll);
  3186. }, this);
  3187. };
  3188. return Navigator;
  3189. }());
  3190. // End of prototype
  3191. if (!H.Navigator) {
  3192. H.Navigator = Navigator;
  3193. NavigatorAxis.compose(Axis);
  3194. // For Stock charts. For x only zooming, do not to create the zoom button
  3195. // because X axis zooming is already allowed by the Navigator and Range
  3196. // selector. (#9285)
  3197. addEvent(Chart, 'beforeShowResetZoom', function () {
  3198. var chartOptions = this.options,
  3199. navigator = chartOptions.navigator,
  3200. rangeSelector = chartOptions.rangeSelector;
  3201. if (((navigator && navigator.enabled) ||
  3202. (rangeSelector && rangeSelector.enabled)) &&
  3203. ((!isTouchDevice && chartOptions.chart.zoomType === 'x') ||
  3204. (isTouchDevice && chartOptions.chart.pinchType === 'x'))) {
  3205. return false;
  3206. }
  3207. });
  3208. // Initialize navigator for stock charts
  3209. addEvent(Chart, 'beforeRender', function () {
  3210. var options = this.options;
  3211. if (options.navigator.enabled ||
  3212. options.scrollbar.enabled) {
  3213. this.scroller = this.navigator = new Navigator(this);
  3214. }
  3215. });
  3216. // For stock charts, extend the Chart.setChartSize method so that we can set
  3217. // the final top position of the navigator once the height of the chart,
  3218. // including the legend, is determined. #367. We can't use Chart.getMargins,
  3219. // because labels offsets are not calculated yet.
  3220. addEvent(Chart, 'afterSetChartSize', function () {
  3221. var legend = this.legend,
  3222. navigator = this.navigator,
  3223. scrollbarHeight,
  3224. legendOptions,
  3225. xAxis,
  3226. yAxis;
  3227. if (navigator) {
  3228. legendOptions = legend && legend.options;
  3229. xAxis = navigator.xAxis;
  3230. yAxis = navigator.yAxis;
  3231. scrollbarHeight = navigator.scrollbarHeight;
  3232. // Compute the top position
  3233. if (this.inverted) {
  3234. navigator.left = navigator.opposite ?
  3235. this.chartWidth - scrollbarHeight -
  3236. navigator.height :
  3237. this.spacing[3] + scrollbarHeight;
  3238. navigator.top = this.plotTop + scrollbarHeight;
  3239. }
  3240. else {
  3241. navigator.left = this.plotLeft + scrollbarHeight;
  3242. navigator.top = navigator.navigatorOptions.top ||
  3243. this.chartHeight -
  3244. navigator.height -
  3245. scrollbarHeight -
  3246. this.spacing[2] -
  3247. (this.rangeSelector && this.extraBottomMargin ?
  3248. this.rangeSelector.getHeight() :
  3249. 0) -
  3250. ((legendOptions &&
  3251. legendOptions.verticalAlign === 'bottom' &&
  3252. legendOptions.layout !== 'proximate' && // #13392
  3253. legendOptions.enabled &&
  3254. !legendOptions.floating) ?
  3255. legend.legendHeight +
  3256. pick(legendOptions.margin, 10) :
  3257. 0) -
  3258. (this.titleOffset ? this.titleOffset[2] : 0);
  3259. }
  3260. if (xAxis && yAxis) { // false if navigator is disabled (#904)
  3261. if (this.inverted) {
  3262. xAxis.options.left = yAxis.options.left = navigator.left;
  3263. }
  3264. else {
  3265. xAxis.options.top = yAxis.options.top = navigator.top;
  3266. }
  3267. xAxis.setAxisSize();
  3268. yAxis.setAxisSize();
  3269. }
  3270. }
  3271. });
  3272. // Merge options, if no scrolling exists yet
  3273. addEvent(Chart, 'update', function (e) {
  3274. var navigatorOptions = (e.options.navigator || {}),
  3275. scrollbarOptions = (e.options.scrollbar || {});
  3276. if (!this.navigator && !this.scroller &&
  3277. (navigatorOptions.enabled || scrollbarOptions.enabled)) {
  3278. merge(true, this.options.navigator, navigatorOptions);
  3279. merge(true, this.options.scrollbar, scrollbarOptions);
  3280. delete e.options.navigator;
  3281. delete e.options.scrollbar;
  3282. }
  3283. });
  3284. // Initialize navigator, if no scrolling exists yet
  3285. addEvent(Chart, 'afterUpdate', function (event) {
  3286. if (!this.navigator && !this.scroller &&
  3287. (this.options.navigator.enabled ||
  3288. this.options.scrollbar.enabled)) {
  3289. this.scroller = this.navigator = new Navigator(this);
  3290. if (pick(event.redraw, true)) {
  3291. this.redraw(event.animation); // #7067
  3292. }
  3293. }
  3294. });
  3295. // Handle adding new series
  3296. addEvent(Chart, 'afterAddSeries', function () {
  3297. if (this.navigator) {
  3298. // Recompute which series should be shown in navigator, and add them
  3299. this.navigator.setBaseSeries(null, false);
  3300. }
  3301. });
  3302. // Handle updating series
  3303. addEvent(Series, 'afterUpdate', function () {
  3304. if (this.chart.navigator && !this.options.isInternal) {
  3305. this.chart.navigator.setBaseSeries(null, false);
  3306. }
  3307. });
  3308. Chart.prototype.callbacks.push(function (chart) {
  3309. var extremes,
  3310. navigator = chart.navigator;
  3311. // Initialize the navigator
  3312. if (navigator && chart.xAxis[0]) {
  3313. extremes = chart.xAxis[0].getExtremes();
  3314. navigator.render(extremes.min, extremes.max);
  3315. }
  3316. });
  3317. }
  3318. H.Navigator = Navigator;
  3319. return H.Navigator;
  3320. });
  3321. _registerModule(_modules, 'Core/Axis/OrdinalAxis.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (Axis, H, U) {
  3322. /* *
  3323. *
  3324. * (c) 2010-2020 Torstein Honsi
  3325. *
  3326. * License: www.highcharts.com/license
  3327. *
  3328. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  3329. *
  3330. * */
  3331. var addEvent = U.addEvent,
  3332. css = U.css,
  3333. defined = U.defined,
  3334. pick = U.pick,
  3335. timeUnits = U.timeUnits;
  3336. // Has a dependency on Navigator due to the use of Axis.toFixedRange
  3337. var Chart = H.Chart,
  3338. Series = H.Series;
  3339. /**
  3340. * Extends the axis with ordinal support.
  3341. * @private
  3342. */
  3343. var OrdinalAxis;
  3344. (function (OrdinalAxis) {
  3345. /* *
  3346. *
  3347. * Classes
  3348. *
  3349. * */
  3350. /**
  3351. * @private
  3352. */
  3353. var Composition = /** @class */ (function () {
  3354. /* *
  3355. *
  3356. * Constructors
  3357. *
  3358. * */
  3359. /**
  3360. * @private
  3361. */
  3362. function Composition(axis) {
  3363. this.index = {};
  3364. this.axis = axis;
  3365. }
  3366. /* *
  3367. *
  3368. * Functions
  3369. *
  3370. * */
  3371. /**
  3372. * Calculate the ordinal positions before tick positions are calculated.
  3373. *
  3374. * @private
  3375. */
  3376. Composition.prototype.beforeSetTickPositions = function () {
  3377. var axis = this.axis,
  3378. ordinal = axis.ordinal,
  3379. len,
  3380. ordinalPositions = [],
  3381. uniqueOrdinalPositions,
  3382. useOrdinal = false,
  3383. dist,
  3384. extremes = axis.getExtremes(),
  3385. min = extremes.min,
  3386. max = extremes.max,
  3387. minIndex,
  3388. maxIndex,
  3389. slope,
  3390. hasBreaks = axis.isXAxis && !!axis.options.breaks,
  3391. isOrdinal = axis.options.ordinal,
  3392. overscrollPointsRange = Number.MAX_VALUE,
  3393. ignoreHiddenSeries = axis.chart.options.chart.ignoreHiddenSeries,
  3394. i,
  3395. hasBoostedSeries;
  3396. // Apply the ordinal logic
  3397. if (isOrdinal || hasBreaks) { // #4167 YAxis is never ordinal ?
  3398. axis.series.forEach(function (series, i) {
  3399. uniqueOrdinalPositions = [];
  3400. if ((!ignoreHiddenSeries || series.visible !== false) &&
  3401. (series.takeOrdinalPosition !== false || hasBreaks)) {
  3402. // concatenate the processed X data into the existing
  3403. // positions, or the empty array
  3404. ordinalPositions = ordinalPositions.concat(series.processedXData);
  3405. len = ordinalPositions.length;
  3406. // remove duplicates (#1588)
  3407. ordinalPositions.sort(function (a, b) {
  3408. // without a custom function it is sorted as strings
  3409. return a - b;
  3410. });
  3411. overscrollPointsRange = Math.min(overscrollPointsRange, pick(
  3412. // Check for a single-point series:
  3413. series.closestPointRange, overscrollPointsRange));
  3414. if (len) {
  3415. i = 0;
  3416. while (i < len - 1) {
  3417. if (ordinalPositions[i] !== ordinalPositions[i + 1]) {
  3418. uniqueOrdinalPositions.push(ordinalPositions[i + 1]);
  3419. }
  3420. i++;
  3421. }
  3422. // Check first item:
  3423. if (uniqueOrdinalPositions[0] !== ordinalPositions[0]) {
  3424. uniqueOrdinalPositions.unshift(ordinalPositions[0]);
  3425. }
  3426. ordinalPositions = uniqueOrdinalPositions;
  3427. }
  3428. }
  3429. if (series.isSeriesBoosting) {
  3430. hasBoostedSeries = true;
  3431. }
  3432. });
  3433. if (hasBoostedSeries) {
  3434. ordinalPositions.length = 0;
  3435. }
  3436. // cache the length
  3437. len = ordinalPositions.length;
  3438. // Check if we really need the overhead of mapping axis data
  3439. // against the ordinal positions. If the series consist of
  3440. // evenly spaced data any way, we don't need any ordinal logic.
  3441. if (len > 2) { // two points have equal distance by default
  3442. dist = ordinalPositions[1] - ordinalPositions[0];
  3443. i = len - 1;
  3444. while (i-- && !useOrdinal) {
  3445. if (ordinalPositions[i + 1] - ordinalPositions[i] !== dist) {
  3446. useOrdinal = true;
  3447. }
  3448. }
  3449. // When zooming in on a week, prevent axis padding for
  3450. // weekends even though the data within the week is evenly
  3451. // spaced.
  3452. if (!axis.options.keepOrdinalPadding &&
  3453. (ordinalPositions[0] - min > dist ||
  3454. max - ordinalPositions[ordinalPositions.length - 1] >
  3455. dist)) {
  3456. useOrdinal = true;
  3457. }
  3458. }
  3459. else if (axis.options.overscroll) {
  3460. if (len === 2) {
  3461. // Exactly two points, distance for overscroll is fixed:
  3462. overscrollPointsRange =
  3463. ordinalPositions[1] - ordinalPositions[0];
  3464. }
  3465. else if (len === 1) {
  3466. // We have just one point, closest distance is unknown.
  3467. // Assume then it is last point and overscrolled range:
  3468. overscrollPointsRange = axis.options.overscroll;
  3469. ordinalPositions = [
  3470. ordinalPositions[0],
  3471. ordinalPositions[0] + overscrollPointsRange
  3472. ];
  3473. }
  3474. else {
  3475. // In case of zooming in on overscrolled range, stick to
  3476. // the old range:
  3477. overscrollPointsRange = ordinal.overscrollPointsRange;
  3478. }
  3479. }
  3480. // Record the slope and offset to compute the linear values from
  3481. // the array index. Since the ordinal positions may exceed the
  3482. // current range, get the start and end positions within it
  3483. // (#719, #665b)
  3484. if (useOrdinal) {
  3485. if (axis.options.overscroll) {
  3486. ordinal.overscrollPointsRange = overscrollPointsRange;
  3487. ordinalPositions = ordinalPositions.concat(ordinal.getOverscrollPositions());
  3488. }
  3489. // Register
  3490. ordinal.positions = ordinalPositions;
  3491. // This relies on the ordinalPositions being set. Use
  3492. // Math.max and Math.min to prevent padding on either sides
  3493. // of the data.
  3494. minIndex = axis.ordinal2lin(// #5979
  3495. Math.max(min, ordinalPositions[0]), true);
  3496. maxIndex = Math.max(axis.ordinal2lin(Math.min(max, ordinalPositions[ordinalPositions.length - 1]), true), 1); // #3339
  3497. // Set the slope and offset of the values compared to the
  3498. // indices in the ordinal positions
  3499. ordinal.slope = slope = (max - min) / (maxIndex - minIndex);
  3500. ordinal.offset = min - (minIndex * slope);
  3501. }
  3502. else {
  3503. ordinal.overscrollPointsRange = pick(axis.closestPointRange, ordinal.overscrollPointsRange);
  3504. ordinal.positions = axis.ordinal.slope = ordinal.offset =
  3505. void 0;
  3506. }
  3507. }
  3508. axis.isOrdinal = isOrdinal && useOrdinal; // #3818, #4196, #4926
  3509. ordinal.groupIntervalFactor = null; // reset for next run
  3510. };
  3511. /**
  3512. * Get the ordinal positions for the entire data set. This is necessary
  3513. * in chart panning because we need to find out what points or data
  3514. * groups are available outside the visible range. When a panning
  3515. * operation starts, if an index for the given grouping does not exists,
  3516. * it is created and cached. This index is deleted on updated data, so
  3517. * it will be regenerated the next time a panning operation starts.
  3518. *
  3519. * @private
  3520. */
  3521. Composition.prototype.getExtendedPositions = function () {
  3522. var ordinal = this,
  3523. axis = ordinal.axis,
  3524. axisProto = axis.constructor.prototype,
  3525. chart = axis.chart,
  3526. grouping = axis.series[0].currentDataGrouping,
  3527. ordinalIndex = ordinal.index,
  3528. key = grouping ?
  3529. grouping.count + grouping.unitName :
  3530. 'raw',
  3531. overscroll = axis.options.overscroll,
  3532. extremes = axis.getExtremes(),
  3533. fakeAxis,
  3534. fakeSeries;
  3535. // If this is the first time, or the ordinal index is deleted by
  3536. // updatedData,
  3537. // create it.
  3538. if (!ordinalIndex) {
  3539. ordinalIndex = ordinal.index = {};
  3540. }
  3541. if (!ordinalIndex[key]) {
  3542. // Create a fake axis object where the extended ordinal
  3543. // positions are emulated
  3544. fakeAxis = {
  3545. series: [],
  3546. chart: chart,
  3547. getExtremes: function () {
  3548. return {
  3549. min: extremes.dataMin,
  3550. max: extremes.dataMax + overscroll
  3551. };
  3552. },
  3553. options: {
  3554. ordinal: true
  3555. },
  3556. ordinal: {},
  3557. ordinal2lin: axisProto.ordinal2lin,
  3558. val2lin: axisProto.val2lin // #2590
  3559. };
  3560. fakeAxis.ordinal.axis = fakeAxis;
  3561. // Add the fake series to hold the full data, then apply
  3562. // processData to it
  3563. axis.series.forEach(function (series) {
  3564. fakeSeries = {
  3565. xAxis: fakeAxis,
  3566. xData: series.xData.slice(),
  3567. chart: chart,
  3568. destroyGroupedData: H.noop,
  3569. getProcessedData: H.Series.prototype.getProcessedData
  3570. };
  3571. fakeSeries.xData = fakeSeries.xData.concat(ordinal.getOverscrollPositions());
  3572. fakeSeries.options = {
  3573. dataGrouping: grouping ? {
  3574. enabled: true,
  3575. forced: true,
  3576. // doesn't matter which, use the fastest
  3577. approximation: 'open',
  3578. units: [[
  3579. grouping.unitName,
  3580. [grouping.count]
  3581. ]]
  3582. } : {
  3583. enabled: false
  3584. }
  3585. };
  3586. series.processData.apply(fakeSeries);
  3587. fakeAxis.series.push(fakeSeries);
  3588. });
  3589. // Run beforeSetTickPositions to compute the ordinalPositions
  3590. axis.ordinal.beforeSetTickPositions.apply({ axis: fakeAxis });
  3591. // Cache it
  3592. ordinalIndex[key] = fakeAxis.ordinal.positions;
  3593. }
  3594. return ordinalIndex[key];
  3595. };
  3596. /**
  3597. * Find the factor to estimate how wide the plot area would have been if
  3598. * ordinal gaps were included. This value is used to compute an imagined
  3599. * plot width in order to establish the data grouping interval.
  3600. *
  3601. * A real world case is the intraday-candlestick example. Without this
  3602. * logic, it would show the correct data grouping when viewing a range
  3603. * within each day, but once moving the range to include the gap between
  3604. * two days, the interval would include the cut-away night hours and the
  3605. * data grouping would be wrong. So the below method tries to compensate
  3606. * by identifying the most common point interval, in this case days.
  3607. *
  3608. * An opposite case is presented in issue #718. We have a long array of
  3609. * daily data, then one point is appended one hour after the last point.
  3610. * We expect the data grouping not to change.
  3611. *
  3612. * In the future, if we find cases where this estimation doesn't work
  3613. * optimally, we might need to add a second pass to the data grouping
  3614. * logic, where we do another run with a greater interval if the number
  3615. * of data groups is more than a certain fraction of the desired group
  3616. * count.
  3617. *
  3618. * @private
  3619. */
  3620. Composition.prototype.getGroupIntervalFactor = function (xMin, xMax, series) {
  3621. var ordinal = this,
  3622. axis = ordinal.axis,
  3623. i,
  3624. processedXData = series.processedXData,
  3625. len = processedXData.length,
  3626. distances = [],
  3627. median,
  3628. groupIntervalFactor = ordinal.groupIntervalFactor;
  3629. // Only do this computation for the first series, let the other
  3630. // inherit it (#2416)
  3631. if (!groupIntervalFactor) {
  3632. // Register all the distances in an array
  3633. for (i = 0; i < len - 1; i++) {
  3634. distances[i] =
  3635. processedXData[i + 1] - processedXData[i];
  3636. }
  3637. // Sort them and find the median
  3638. distances.sort(function (a, b) {
  3639. return a - b;
  3640. });
  3641. median = distances[Math.floor(len / 2)];
  3642. // Compensate for series that don't extend through the entire
  3643. // axis extent. #1675.
  3644. xMin = Math.max(xMin, processedXData[0]);
  3645. xMax = Math.min(xMax, processedXData[len - 1]);
  3646. ordinal.groupIntervalFactor = groupIntervalFactor =
  3647. (len * median) / (xMax - xMin);
  3648. }
  3649. // Return the factor needed for data grouping
  3650. return groupIntervalFactor;
  3651. };
  3652. /**
  3653. * Get ticks for an ordinal axis within a range where points don't
  3654. * exist. It is required when overscroll is enabled. We can't base on
  3655. * points, because we may not have any, so we use approximated
  3656. * pointRange and generate these ticks between Axis.dataMax,
  3657. * Axis.dataMax + Axis.overscroll evenly spaced. Used in panning and
  3658. * navigator scrolling.
  3659. *
  3660. * @private
  3661. */
  3662. Composition.prototype.getOverscrollPositions = function () {
  3663. var ordinal = this,
  3664. axis = ordinal.axis,
  3665. extraRange = axis.options.overscroll,
  3666. distance = ordinal.overscrollPointsRange,
  3667. positions = [],
  3668. max = axis.dataMax;
  3669. if (defined(distance)) {
  3670. // Max + pointRange because we need to scroll to the last
  3671. positions.push(max);
  3672. while (max <= axis.dataMax + extraRange) {
  3673. max += distance;
  3674. positions.push(max);
  3675. }
  3676. }
  3677. return positions;
  3678. };
  3679. /**
  3680. * Make the tick intervals closer because the ordinal gaps make the
  3681. * ticks spread out or cluster.
  3682. *
  3683. * @private
  3684. */
  3685. Composition.prototype.postProcessTickInterval = function (tickInterval) {
  3686. // Problem: https://jsfiddle.net/highcharts/FQm4E/1/
  3687. // This is a case where this algorithm doesn't work optimally. In
  3688. // this case, the tick labels are spread out per week, but all the
  3689. // gaps reside within weeks. So we have a situation where the labels
  3690. // are courser than the ordinal gaps, and thus the tick interval
  3691. // should not be altered.
  3692. var ordinal = this,
  3693. axis = ordinal.axis,
  3694. ordinalSlope = ordinal.slope,
  3695. ret;
  3696. if (ordinalSlope) {
  3697. if (!axis.options.breaks) {
  3698. ret = tickInterval / (ordinalSlope / axis.closestPointRange);
  3699. }
  3700. else {
  3701. ret = axis.closestPointRange || tickInterval; // #7275
  3702. }
  3703. }
  3704. else {
  3705. ret = tickInterval;
  3706. }
  3707. return ret;
  3708. };
  3709. return Composition;
  3710. }());
  3711. OrdinalAxis.Composition = Composition;
  3712. /* *
  3713. *
  3714. * Functions
  3715. *
  3716. * */
  3717. /**
  3718. * Extends the axis with ordinal support.
  3719. *
  3720. * @private
  3721. *
  3722. * @param AxisClass
  3723. * Axis class to extend.
  3724. *
  3725. * @param ChartClass
  3726. * Chart class to use.
  3727. *
  3728. * @param SeriesClass
  3729. * Series class to use.
  3730. */
  3731. function compose(AxisClass, ChartClass, SeriesClass) {
  3732. AxisClass.keepProps.push('ordinal');
  3733. var axisProto = AxisClass.prototype;
  3734. /**
  3735. * In an ordinal axis, there might be areas with dense consentrations of
  3736. * points, then large gaps between some. Creating equally distributed
  3737. * ticks over this entire range may lead to a huge number of ticks that
  3738. * will later be removed. So instead, break the positions up in
  3739. * segments, find the tick positions for each segment then concatenize
  3740. * them. This method is used from both data grouping logic and X axis
  3741. * tick position logic.
  3742. *
  3743. * @private
  3744. */
  3745. AxisClass.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek, positions, closestDistance, findHigherRanks) {
  3746. if (positions === void 0) { positions = []; }
  3747. if (closestDistance === void 0) { closestDistance = 0; }
  3748. var start = 0,
  3749. end,
  3750. segmentPositions,
  3751. higherRanks = {},
  3752. hasCrossedHigherRank,
  3753. info,
  3754. posLength,
  3755. outsideMax,
  3756. groupPositions = [],
  3757. lastGroupPosition = -Number.MAX_VALUE,
  3758. tickPixelIntervalOption = this.options.tickPixelInterval,
  3759. time = this.chart.time,
  3760. // Record all the start positions of a segment, to use when
  3761. // deciding what's a gap in the data.
  3762. segmentStarts = [];
  3763. // The positions are not always defined, for example for ordinal
  3764. // positions when data has regular interval (#1557, #2090)
  3765. if ((!this.options.ordinal && !this.options.breaks) ||
  3766. !positions ||
  3767. positions.length < 3 ||
  3768. typeof min === 'undefined') {
  3769. return time.getTimeTicks.apply(time, arguments);
  3770. }
  3771. // Analyze the positions array to split it into segments on gaps
  3772. // larger than 5 times the closest distance. The closest distance is
  3773. // already found at this point, so we reuse that instead of
  3774. // computing it again.
  3775. posLength = positions.length;
  3776. for (end = 0; end < posLength; end++) {
  3777. outsideMax = end && positions[end - 1] > max;
  3778. if (positions[end] < min) { // Set the last position before min
  3779. start = end;
  3780. }
  3781. if (end === posLength - 1 ||
  3782. positions[end + 1] - positions[end] > closestDistance * 5 ||
  3783. outsideMax) {
  3784. // For each segment, calculate the tick positions from the
  3785. // getTimeTicks utility function. The interval will be the
  3786. // same regardless of how long the segment is.
  3787. if (positions[end] > lastGroupPosition) { // #1475
  3788. segmentPositions = time.getTimeTicks(normalizedInterval, positions[start], positions[end], startOfWeek);
  3789. // Prevent duplicate groups, for example for multiple
  3790. // segments within one larger time frame (#1475)
  3791. while (segmentPositions.length &&
  3792. segmentPositions[0] <= lastGroupPosition) {
  3793. segmentPositions.shift();
  3794. }
  3795. if (segmentPositions.length) {
  3796. lastGroupPosition =
  3797. segmentPositions[segmentPositions.length - 1];
  3798. }
  3799. segmentStarts.push(groupPositions.length);
  3800. groupPositions = groupPositions.concat(segmentPositions);
  3801. }
  3802. // Set start of next segment
  3803. start = end + 1;
  3804. }
  3805. if (outsideMax) {
  3806. break;
  3807. }
  3808. }
  3809. // Get the grouping info from the last of the segments. The info is
  3810. // the same for all segments.
  3811. info = segmentPositions.info;
  3812. // Optionally identify ticks with higher rank, for example when the
  3813. // ticks have crossed midnight.
  3814. if (findHigherRanks && info.unitRange <= timeUnits.hour) {
  3815. end = groupPositions.length - 1;
  3816. // Compare points two by two
  3817. for (start = 1; start < end; start++) {
  3818. if (time.dateFormat('%d', groupPositions[start]) !==
  3819. time.dateFormat('%d', groupPositions[start - 1])) {
  3820. higherRanks[groupPositions[start]] = 'day';
  3821. hasCrossedHigherRank = true;
  3822. }
  3823. }
  3824. // If the complete array has crossed midnight, we want to mark
  3825. // the first positions also as higher rank
  3826. if (hasCrossedHigherRank) {
  3827. higherRanks[groupPositions[0]] = 'day';
  3828. }
  3829. info.higherRanks = higherRanks;
  3830. }
  3831. // Save the info
  3832. info.segmentStarts = segmentStarts;
  3833. groupPositions.info = info;
  3834. // Don't show ticks within a gap in the ordinal axis, where the
  3835. // space between two points is greater than a portion of the tick
  3836. // pixel interval
  3837. if (findHigherRanks && defined(tickPixelIntervalOption)) {
  3838. var length = groupPositions.length,
  3839. i = length,
  3840. itemToRemove,
  3841. translated,
  3842. translatedArr = [],
  3843. lastTranslated,
  3844. medianDistance,
  3845. distance,
  3846. distances = [];
  3847. // Find median pixel distance in order to keep a reasonably even
  3848. // distance between ticks (#748)
  3849. while (i--) {
  3850. translated = this.translate(groupPositions[i]);
  3851. if (lastTranslated) {
  3852. distances[i] = lastTranslated - translated;
  3853. }
  3854. translatedArr[i] = lastTranslated = translated;
  3855. }
  3856. distances.sort();
  3857. medianDistance = distances[Math.floor(distances.length / 2)];
  3858. if (medianDistance < tickPixelIntervalOption * 0.6) {
  3859. medianDistance = null;
  3860. }
  3861. // Now loop over again and remove ticks where needed
  3862. i = groupPositions[length - 1] > max ? length - 1 : length; // #817
  3863. lastTranslated = void 0;
  3864. while (i--) {
  3865. translated = translatedArr[i];
  3866. distance = Math.abs(lastTranslated - translated);
  3867. // #4175 - when axis is reversed, the distance, is negative
  3868. // but tickPixelIntervalOption positive, so we need to
  3869. // compare the same values
  3870. // Remove ticks that are closer than 0.6 times the pixel
  3871. // interval from the one to the right, but not if it is
  3872. // close to the median distance (#748).
  3873. if (lastTranslated &&
  3874. distance < tickPixelIntervalOption * 0.8 &&
  3875. (medianDistance === null || distance < medianDistance * 0.8)) {
  3876. // Is this a higher ranked position with a normal
  3877. // position to the right?
  3878. if (higherRanks[groupPositions[i]] &&
  3879. !higherRanks[groupPositions[i + 1]]) {
  3880. // Yes: remove the lower ranked neighbour to the
  3881. // right
  3882. itemToRemove = i + 1;
  3883. lastTranslated = translated; // #709
  3884. }
  3885. else {
  3886. // No: remove this one
  3887. itemToRemove = i;
  3888. }
  3889. groupPositions.splice(itemToRemove, 1);
  3890. }
  3891. else {
  3892. lastTranslated = translated;
  3893. }
  3894. }
  3895. }
  3896. return groupPositions;
  3897. };
  3898. /**
  3899. * Translate from linear (internal) to axis value.
  3900. *
  3901. * @private
  3902. * @function Highcharts.Axis#lin2val
  3903. *
  3904. * @param {number} val
  3905. * The linear abstracted value.
  3906. *
  3907. * @param {boolean} [fromIndex]
  3908. * Translate from an index in the ordinal positions rather than a
  3909. * value.
  3910. *
  3911. * @return {number}
  3912. */
  3913. axisProto.lin2val = function (val, fromIndex) {
  3914. var axis = this,
  3915. ordinal = axis.ordinal,
  3916. ordinalPositions = ordinal.positions,
  3917. ret;
  3918. // the visible range contains only equally spaced values
  3919. if (!ordinalPositions) {
  3920. ret = val;
  3921. }
  3922. else {
  3923. var ordinalSlope = ordinal.slope,
  3924. ordinalOffset = ordinal.offset,
  3925. i = ordinalPositions.length - 1,
  3926. linearEquivalentLeft,
  3927. linearEquivalentRight,
  3928. distance;
  3929. // Handle the case where we translate from the index directly,
  3930. // used only when panning an ordinal axis
  3931. if (fromIndex) {
  3932. if (val < 0) { // out of range, in effect panning to the left
  3933. val = ordinalPositions[0];
  3934. }
  3935. else if (val > i) { // out of range, panning to the right
  3936. val = ordinalPositions[i];
  3937. }
  3938. else { // split it up
  3939. i = Math.floor(val);
  3940. distance = val - i; // the decimal
  3941. }
  3942. // Loop down along the ordinal positions. When the linear
  3943. // equivalent of i matches an ordinal position, interpolate
  3944. // between the left and right values.
  3945. }
  3946. else {
  3947. while (i--) {
  3948. linearEquivalentLeft =
  3949. (ordinalSlope * i) + ordinalOffset;
  3950. if (val >= linearEquivalentLeft) {
  3951. linearEquivalentRight =
  3952. (ordinalSlope *
  3953. (i + 1)) +
  3954. ordinalOffset;
  3955. // something between 0 and 1
  3956. distance = (val - linearEquivalentLeft) /
  3957. (linearEquivalentRight - linearEquivalentLeft);
  3958. break;
  3959. }
  3960. }
  3961. }
  3962. // If the index is within the range of the ordinal positions,
  3963. // return the associated or interpolated value. If not, just
  3964. // return the value.
  3965. return (typeof distance !== 'undefined' &&
  3966. typeof ordinalPositions[i] !== 'undefined' ?
  3967. ordinalPositions[i] + (distance ?
  3968. distance *
  3969. (ordinalPositions[i + 1] - ordinalPositions[i]) :
  3970. 0) :
  3971. val);
  3972. }
  3973. return ret;
  3974. };
  3975. /**
  3976. * Translate from a linear axis value to the corresponding ordinal axis
  3977. * position. If there are no gaps in the ordinal axis this will be the
  3978. * same. The translated value is the value that the point would have if
  3979. * the axis were linear, using the same min and max.
  3980. *
  3981. * @private
  3982. * @function Highcharts.Axis#val2lin
  3983. *
  3984. * @param {number} val
  3985. * The axis value.
  3986. *
  3987. * @param {boolean} [toIndex]
  3988. * Whether to return the index in the ordinalPositions or the new value.
  3989. *
  3990. * @return {number}
  3991. */
  3992. axisProto.val2lin = function (val, toIndex) {
  3993. var axis = this,
  3994. ordinal = axis.ordinal,
  3995. ordinalPositions = ordinal.positions,
  3996. ret;
  3997. if (!ordinalPositions) {
  3998. ret = val;
  3999. }
  4000. else {
  4001. var ordinalLength = ordinalPositions.length,
  4002. i,
  4003. distance,
  4004. ordinalIndex;
  4005. // first look for an exact match in the ordinalpositions array
  4006. i = ordinalLength;
  4007. while (i--) {
  4008. if (ordinalPositions[i] === val) {
  4009. ordinalIndex = i;
  4010. break;
  4011. }
  4012. }
  4013. // if that failed, find the intermediate position between the
  4014. // two nearest values
  4015. i = ordinalLength - 1;
  4016. while (i--) {
  4017. if (val > ordinalPositions[i] || i === 0) { // interpolate
  4018. // something between 0 and 1
  4019. distance = (val - ordinalPositions[i]) /
  4020. (ordinalPositions[i + 1] - ordinalPositions[i]);
  4021. ordinalIndex = i + distance;
  4022. break;
  4023. }
  4024. }
  4025. ret = toIndex ?
  4026. ordinalIndex :
  4027. ordinal.slope *
  4028. (ordinalIndex || 0) +
  4029. ordinal.offset;
  4030. }
  4031. return ret;
  4032. };
  4033. // Record this to prevent overwriting by broken-axis module (#5979)
  4034. axisProto.ordinal2lin = axisProto.val2lin;
  4035. /* eslint-disable no-invalid-this */
  4036. addEvent(AxisClass, 'afterInit', function () {
  4037. var axis = this;
  4038. if (!axis.ordinal) {
  4039. axis.ordinal = new OrdinalAxis.Composition(axis);
  4040. }
  4041. });
  4042. addEvent(AxisClass, 'foundExtremes', function () {
  4043. var axis = this;
  4044. if (axis.isXAxis &&
  4045. defined(axis.options.overscroll) &&
  4046. axis.max === axis.dataMax &&
  4047. (
  4048. // Panning is an execption. We don't want to apply
  4049. // overscroll when panning over the dataMax
  4050. !axis.chart.mouseIsDown ||
  4051. axis.isInternal) && (
  4052. // Scrollbar buttons are the other execption:
  4053. !axis.eventArgs ||
  4054. axis.eventArgs && axis.eventArgs.trigger !== 'navigator')) {
  4055. axis.max += axis.options.overscroll;
  4056. // Live data and buttons require translation for the min:
  4057. if (!axis.isInternal && defined(axis.userMin)) {
  4058. axis.min += axis.options.overscroll;
  4059. }
  4060. }
  4061. });
  4062. // For ordinal axis, that loads data async, redraw axis after data is
  4063. // loaded. If we don't do that, axis will have the same extremes as
  4064. // previously, but ordinal positions won't be calculated. See #10290
  4065. addEvent(AxisClass, 'afterSetScale', function () {
  4066. var axis = this;
  4067. if (axis.horiz && !axis.isDirty) {
  4068. axis.isDirty = axis.isOrdinal &&
  4069. axis.chart.navigator &&
  4070. !axis.chart.navigator.adaptToUpdatedData;
  4071. }
  4072. });
  4073. addEvent(AxisClass, 'initialAxisTranslation', function () {
  4074. var axis = this;
  4075. if (axis.ordinal) {
  4076. axis.ordinal.beforeSetTickPositions();
  4077. axis.tickInterval = axis.ordinal.postProcessTickInterval(axis.tickInterval);
  4078. }
  4079. });
  4080. // Extending the Chart.pan method for ordinal axes
  4081. addEvent(ChartClass, 'pan', function (e) {
  4082. var chart = this,
  4083. xAxis = chart.xAxis[0],
  4084. overscroll = xAxis.options.overscroll,
  4085. chartX = e.originalEvent.chartX,
  4086. panning = chart.options.chart &&
  4087. chart.options.chart.panning,
  4088. runBase = false;
  4089. if (panning &&
  4090. panning.type !== 'y' &&
  4091. xAxis.options.ordinal &&
  4092. xAxis.series.length) {
  4093. var mouseDownX = chart.mouseDownX,
  4094. extremes = xAxis.getExtremes(),
  4095. dataMax = extremes.dataMax,
  4096. min = extremes.min,
  4097. max = extremes.max,
  4098. trimmedRange,
  4099. hoverPoints = chart.hoverPoints,
  4100. closestPointRange = (xAxis.closestPointRange ||
  4101. (xAxis.ordinal && xAxis.ordinal.overscrollPointsRange)),
  4102. pointPixelWidth = (xAxis.translationSlope *
  4103. (xAxis.ordinal.slope || closestPointRange)),
  4104. // how many ordinal units did we move?
  4105. movedUnits = (mouseDownX - chartX) / pointPixelWidth,
  4106. // get index of all the chart's points
  4107. extendedAxis = { ordinal: { positions: xAxis.ordinal.getExtendedPositions() } },
  4108. ordinalPositions,
  4109. searchAxisLeft,
  4110. lin2val = xAxis.lin2val,
  4111. val2lin = xAxis.val2lin,
  4112. searchAxisRight;
  4113. // we have an ordinal axis, but the data is equally spaced
  4114. if (!extendedAxis.ordinal.positions) {
  4115. runBase = true;
  4116. }
  4117. else if (Math.abs(movedUnits) > 1) {
  4118. // Remove active points for shared tooltip
  4119. if (hoverPoints) {
  4120. hoverPoints.forEach(function (point) {
  4121. point.setState();
  4122. });
  4123. }
  4124. if (movedUnits < 0) {
  4125. searchAxisLeft = extendedAxis;
  4126. searchAxisRight = xAxis.ordinal.positions ? xAxis : extendedAxis;
  4127. }
  4128. else {
  4129. searchAxisLeft = xAxis.ordinal.positions ? xAxis : extendedAxis;
  4130. searchAxisRight = extendedAxis;
  4131. }
  4132. // In grouped data series, the last ordinal position
  4133. // represents the grouped data, which is to the left of the
  4134. // real data max. If we don't compensate for this, we will
  4135. // be allowed to pan grouped data series passed the right of
  4136. // the plot area.
  4137. ordinalPositions = searchAxisRight.ordinal.positions;
  4138. if (dataMax >
  4139. ordinalPositions[ordinalPositions.length - 1]) {
  4140. ordinalPositions.push(dataMax);
  4141. }
  4142. // Get the new min and max values by getting the ordinal
  4143. // index for the current extreme, then add the moved units
  4144. // and translate back to values. This happens on the
  4145. // extended ordinal positions if the new position is out of
  4146. // range, else it happens on the current x axis which is
  4147. // smaller and faster.
  4148. chart.fixedRange = max - min;
  4149. trimmedRange = xAxis.navigatorAxis.toFixedRange(null, null, lin2val.apply(searchAxisLeft, [
  4150. val2lin.apply(searchAxisLeft, [min, true]) + movedUnits,
  4151. true // translate from index
  4152. ]), lin2val.apply(searchAxisRight, [
  4153. val2lin.apply(searchAxisRight, [max, true]) + movedUnits,
  4154. true // translate from index
  4155. ]));
  4156. // Apply it if it is within the available data range
  4157. if (trimmedRange.min >= Math.min(extremes.dataMin, min) &&
  4158. trimmedRange.max <= Math.max(dataMax, max) + overscroll) {
  4159. xAxis.setExtremes(trimmedRange.min, trimmedRange.max, true, false, { trigger: 'pan' });
  4160. }
  4161. chart.mouseDownX = chartX; // set new reference for next run
  4162. css(chart.container, { cursor: 'move' });
  4163. }
  4164. }
  4165. else {
  4166. runBase = true;
  4167. }
  4168. // revert to the linear chart.pan version
  4169. if (runBase || (panning && /y/.test(panning.type))) {
  4170. if (overscroll) {
  4171. xAxis.max = xAxis.dataMax + overscroll;
  4172. }
  4173. }
  4174. else {
  4175. e.preventDefault();
  4176. }
  4177. });
  4178. addEvent(SeriesClass, 'updatedData', function () {
  4179. var xAxis = this.xAxis;
  4180. // Destroy the extended ordinal index on updated data
  4181. if (xAxis && xAxis.options.ordinal) {
  4182. delete xAxis.ordinal.index;
  4183. }
  4184. });
  4185. /* eslint-enable no-invalid-this */
  4186. }
  4187. OrdinalAxis.compose = compose;
  4188. })(OrdinalAxis || (OrdinalAxis = {}));
  4189. OrdinalAxis.compose(Axis, Chart, Series); // @todo move to StockChart, remove from master
  4190. return OrdinalAxis;
  4191. });
  4192. _registerModule(_modules, 'Core/Axis/BrokenAxis.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Extensions/Stacking.js']], function (Axis, H, U, StackItem) {
  4193. /* *
  4194. *
  4195. * (c) 2009-2020 Torstein Honsi
  4196. *
  4197. * License: www.highcharts.com/license
  4198. *
  4199. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  4200. *
  4201. * */
  4202. var addEvent = U.addEvent,
  4203. find = U.find,
  4204. fireEvent = U.fireEvent,
  4205. isArray = U.isArray,
  4206. isNumber = U.isNumber,
  4207. pick = U.pick;
  4208. var Series = H.Series;
  4209. /* eslint-disable valid-jsdoc */
  4210. /**
  4211. * Provides support for broken axes.
  4212. * @private
  4213. * @class
  4214. */
  4215. var BrokenAxisAdditions = /** @class */ (function () {
  4216. /* *
  4217. *
  4218. * Constructors
  4219. *
  4220. * */
  4221. function BrokenAxisAdditions(axis) {
  4222. this.hasBreaks = false;
  4223. this.axis = axis;
  4224. }
  4225. /* *
  4226. *
  4227. * Static Functions
  4228. *
  4229. * */
  4230. /**
  4231. * @private
  4232. */
  4233. BrokenAxisAdditions.isInBreak = function (brk, val) {
  4234. var ret,
  4235. repeat = brk.repeat || Infinity,
  4236. from = brk.from,
  4237. length = brk.to - brk.from,
  4238. test = (val >= from ?
  4239. (val - from) % repeat :
  4240. repeat - ((from - val) % repeat));
  4241. if (!brk.inclusive) {
  4242. ret = test < length && test !== 0;
  4243. }
  4244. else {
  4245. ret = test <= length;
  4246. }
  4247. return ret;
  4248. };
  4249. /**
  4250. * @private
  4251. */
  4252. BrokenAxisAdditions.lin2Val = function (val) {
  4253. var axis = this;
  4254. var brokenAxis = axis.brokenAxis;
  4255. var breakArray = brokenAxis && brokenAxis.breakArray;
  4256. if (!breakArray) {
  4257. return val;
  4258. }
  4259. var nval = val,
  4260. brk,
  4261. i;
  4262. for (i = 0; i < breakArray.length; i++) {
  4263. brk = breakArray[i];
  4264. if (brk.from >= nval) {
  4265. break;
  4266. }
  4267. else if (brk.to < nval) {
  4268. nval += brk.len;
  4269. }
  4270. else if (BrokenAxisAdditions.isInBreak(brk, nval)) {
  4271. nval += brk.len;
  4272. }
  4273. }
  4274. return nval;
  4275. };
  4276. /**
  4277. * @private
  4278. */
  4279. BrokenAxisAdditions.val2Lin = function (val) {
  4280. var axis = this;
  4281. var brokenAxis = axis.brokenAxis;
  4282. var breakArray = brokenAxis && brokenAxis.breakArray;
  4283. if (!breakArray) {
  4284. return val;
  4285. }
  4286. var nval = val,
  4287. brk,
  4288. i;
  4289. for (i = 0; i < breakArray.length; i++) {
  4290. brk = breakArray[i];
  4291. if (brk.to <= val) {
  4292. nval -= brk.len;
  4293. }
  4294. else if (brk.from >= val) {
  4295. break;
  4296. }
  4297. else if (BrokenAxisAdditions.isInBreak(brk, val)) {
  4298. nval -= (val - brk.from);
  4299. break;
  4300. }
  4301. }
  4302. return nval;
  4303. };
  4304. /* *
  4305. *
  4306. * Functions
  4307. *
  4308. * */
  4309. /**
  4310. * Returns the first break found where the x is larger then break.from and
  4311. * smaller then break.to.
  4312. *
  4313. * @param {number} x
  4314. * The number which should be within a break.
  4315. *
  4316. * @param {Array<Highcharts.XAxisBreaksOptions>} breaks
  4317. * The array of breaks to search within.
  4318. *
  4319. * @return {Highcharts.XAxisBreaksOptions|undefined}
  4320. * Returns the first break found that matches, returns false if no break is
  4321. * found.
  4322. */
  4323. BrokenAxisAdditions.prototype.findBreakAt = function (x, breaks) {
  4324. return find(breaks, function (b) {
  4325. return b.from < x && x < b.to;
  4326. });
  4327. };
  4328. /**
  4329. * @private
  4330. */
  4331. BrokenAxisAdditions.prototype.isInAnyBreak = function (val, testKeep) {
  4332. var brokenAxis = this;
  4333. var axis = brokenAxis.axis;
  4334. var breaks = axis.options.breaks,
  4335. i = breaks && breaks.length,
  4336. inbrk,
  4337. keep,
  4338. ret;
  4339. if (i) {
  4340. while (i--) {
  4341. if (BrokenAxisAdditions.isInBreak(breaks[i], val)) {
  4342. inbrk = true;
  4343. if (!keep) {
  4344. keep = pick(breaks[i].showPoints, !axis.isXAxis);
  4345. }
  4346. }
  4347. }
  4348. if (inbrk && testKeep) {
  4349. ret = inbrk && !keep;
  4350. }
  4351. else {
  4352. ret = inbrk;
  4353. }
  4354. }
  4355. return ret;
  4356. };
  4357. /**
  4358. * Dynamically set or unset breaks in an axis. This function in lighter than
  4359. * usin Axis.update, and it also preserves animation.
  4360. *
  4361. * @private
  4362. * @function Highcharts.Axis#setBreaks
  4363. *
  4364. * @param {Array<Highcharts.XAxisBreaksOptions>} [breaks]
  4365. * The breaks to add. When `undefined` it removes existing breaks.
  4366. *
  4367. * @param {boolean} [redraw=true]
  4368. * Whether to redraw the chart immediately.
  4369. *
  4370. * @return {void}
  4371. */
  4372. BrokenAxisAdditions.prototype.setBreaks = function (breaks, redraw) {
  4373. var brokenAxis = this;
  4374. var axis = brokenAxis.axis;
  4375. var hasBreaks = (isArray(breaks) && !!breaks.length);
  4376. axis.isDirty = brokenAxis.hasBreaks !== hasBreaks;
  4377. brokenAxis.hasBreaks = hasBreaks;
  4378. axis.options.breaks = axis.userOptions.breaks = breaks;
  4379. axis.forceRedraw = true; // Force recalculation in setScale
  4380. // Recalculate series related to the axis.
  4381. axis.series.forEach(function (series) {
  4382. series.isDirty = true;
  4383. });
  4384. if (!hasBreaks && axis.val2lin === BrokenAxisAdditions.val2Lin) {
  4385. // Revert to prototype functions
  4386. delete axis.val2lin;
  4387. delete axis.lin2val;
  4388. }
  4389. if (hasBreaks) {
  4390. axis.userOptions.ordinal = false;
  4391. axis.lin2val = BrokenAxisAdditions.lin2Val;
  4392. axis.val2lin = BrokenAxisAdditions.val2Lin;
  4393. axis.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) {
  4394. // If trying to set extremes inside a break, extend min to
  4395. // after, and max to before the break ( #3857 )
  4396. if (brokenAxis.hasBreaks) {
  4397. var axisBreak,
  4398. breaks = this.options.breaks;
  4399. while ((axisBreak = brokenAxis.findBreakAt(newMin, breaks))) {
  4400. newMin = axisBreak.to;
  4401. }
  4402. while ((axisBreak = brokenAxis.findBreakAt(newMax, breaks))) {
  4403. newMax = axisBreak.from;
  4404. }
  4405. // If both min and max is within the same break.
  4406. if (newMax < newMin) {
  4407. newMax = newMin;
  4408. }
  4409. }
  4410. Axis.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments);
  4411. };
  4412. axis.setAxisTranslation = function (saveOld) {
  4413. Axis.prototype.setAxisTranslation.call(this, saveOld);
  4414. brokenAxis.unitLength = null;
  4415. if (brokenAxis.hasBreaks) {
  4416. var breaks = axis.options.breaks || [],
  4417. // Temporary one:
  4418. breakArrayT = [],
  4419. breakArray = [],
  4420. length = 0,
  4421. inBrk,
  4422. repeat,
  4423. min = axis.userMin || axis.min,
  4424. max = axis.userMax || axis.max,
  4425. pointRangePadding = pick(axis.pointRangePadding, 0),
  4426. start,
  4427. i;
  4428. // Min & max check (#4247)
  4429. breaks.forEach(function (brk) {
  4430. repeat = brk.repeat || Infinity;
  4431. if (BrokenAxisAdditions.isInBreak(brk, min)) {
  4432. min +=
  4433. (brk.to % repeat) -
  4434. (min % repeat);
  4435. }
  4436. if (BrokenAxisAdditions.isInBreak(brk, max)) {
  4437. max -=
  4438. (max % repeat) -
  4439. (brk.from % repeat);
  4440. }
  4441. });
  4442. // Construct an array holding all breaks in the axis
  4443. breaks.forEach(function (brk) {
  4444. start = brk.from;
  4445. repeat = brk.repeat || Infinity;
  4446. while (start - repeat > min) {
  4447. start -= repeat;
  4448. }
  4449. while (start < min) {
  4450. start += repeat;
  4451. }
  4452. for (i = start; i < max; i += repeat) {
  4453. breakArrayT.push({
  4454. value: i,
  4455. move: 'in'
  4456. });
  4457. breakArrayT.push({
  4458. value: i + (brk.to - brk.from),
  4459. move: 'out',
  4460. size: brk.breakSize
  4461. });
  4462. }
  4463. });
  4464. breakArrayT.sort(function (a, b) {
  4465. return ((a.value === b.value) ?
  4466. ((a.move === 'in' ? 0 : 1) -
  4467. (b.move === 'in' ? 0 : 1)) :
  4468. a.value - b.value);
  4469. });
  4470. // Simplify the breaks
  4471. inBrk = 0;
  4472. start = min;
  4473. breakArrayT.forEach(function (brk) {
  4474. inBrk += (brk.move === 'in' ? 1 : -1);
  4475. if (inBrk === 1 && brk.move === 'in') {
  4476. start = brk.value;
  4477. }
  4478. if (inBrk === 0) {
  4479. breakArray.push({
  4480. from: start,
  4481. to: brk.value,
  4482. len: brk.value - start - (brk.size || 0)
  4483. });
  4484. length += brk.value - start - (brk.size || 0);
  4485. }
  4486. });
  4487. /**
  4488. * HC <= 8 backwards compatibility, used by demo samples.
  4489. * @deprecated
  4490. * @private
  4491. * @requires modules/broken-axis
  4492. */
  4493. axis.breakArray = brokenAxis.breakArray = breakArray;
  4494. // Used with staticScale, and below the actual axis length,
  4495. // when breaks are substracted.
  4496. brokenAxis.unitLength = max - min - length + pointRangePadding;
  4497. fireEvent(axis, 'afterBreaks');
  4498. if (axis.staticScale) {
  4499. axis.transA = axis.staticScale;
  4500. }
  4501. else if (brokenAxis.unitLength) {
  4502. axis.transA *=
  4503. (max - axis.min + pointRangePadding) /
  4504. brokenAxis.unitLength;
  4505. }
  4506. if (pointRangePadding) {
  4507. axis.minPixelPadding =
  4508. axis.transA * axis.minPointOffset;
  4509. }
  4510. axis.min = min;
  4511. axis.max = max;
  4512. }
  4513. };
  4514. }
  4515. if (pick(redraw, true)) {
  4516. axis.chart.redraw();
  4517. }
  4518. };
  4519. return BrokenAxisAdditions;
  4520. }());
  4521. /**
  4522. * Axis with support of broken data rows.
  4523. * @private
  4524. * @class
  4525. */
  4526. var BrokenAxis = /** @class */ (function () {
  4527. function BrokenAxis() {
  4528. }
  4529. /**
  4530. * Adds support for broken axes.
  4531. * @private
  4532. */
  4533. BrokenAxis.compose = function (AxisClass, SeriesClass) {
  4534. AxisClass.keepProps.push('brokenAxis');
  4535. var seriesProto = Series.prototype;
  4536. /**
  4537. * @private
  4538. */
  4539. seriesProto.drawBreaks = function (axis, keys) {
  4540. var series = this,
  4541. points = series.points,
  4542. breaks,
  4543. threshold,
  4544. eventName,
  4545. y;
  4546. if (axis && // #5950
  4547. axis.brokenAxis &&
  4548. axis.brokenAxis.hasBreaks) {
  4549. var brokenAxis_1 = axis.brokenAxis;
  4550. keys.forEach(function (key) {
  4551. breaks = brokenAxis_1 && brokenAxis_1.breakArray || [];
  4552. threshold = axis.isXAxis ?
  4553. axis.min :
  4554. pick(series.options.threshold, axis.min);
  4555. points.forEach(function (point) {
  4556. y = pick(point['stack' + key.toUpperCase()], point[key]);
  4557. breaks.forEach(function (brk) {
  4558. if (isNumber(threshold) && isNumber(y)) {
  4559. eventName = false;
  4560. if ((threshold < brk.from && y > brk.to) ||
  4561. (threshold > brk.from && y < brk.from)) {
  4562. eventName = 'pointBreak';
  4563. }
  4564. else if ((threshold < brk.from && y > brk.from && y < brk.to) ||
  4565. (threshold > brk.from && y > brk.to && y < brk.from)) {
  4566. eventName = 'pointInBreak';
  4567. }
  4568. if (eventName) {
  4569. fireEvent(axis, eventName, { point: point, brk: brk });
  4570. }
  4571. }
  4572. });
  4573. });
  4574. });
  4575. }
  4576. };
  4577. /**
  4578. * Extend getGraphPath by identifying gaps in the data so that we can
  4579. * draw a gap in the line or area. This was moved from ordinal axis
  4580. * module to broken axis module as of #5045.
  4581. *
  4582. * @private
  4583. * @function Highcharts.Series#gappedPath
  4584. *
  4585. * @return {Highcharts.SVGPathArray}
  4586. * Gapped path
  4587. */
  4588. seriesProto.gappedPath = function () {
  4589. var currentDataGrouping = this.currentDataGrouping,
  4590. groupingSize = currentDataGrouping && currentDataGrouping.gapSize,
  4591. gapSize = this.options.gapSize,
  4592. points = this.points.slice(),
  4593. i = points.length - 1,
  4594. yAxis = this.yAxis,
  4595. stack;
  4596. /**
  4597. * Defines when to display a gap in the graph, together with the
  4598. * [gapUnit](plotOptions.series.gapUnit) option.
  4599. *
  4600. * In case when `dataGrouping` is enabled, points can be grouped
  4601. * into a larger time span. This can make the grouped points to have
  4602. * a greater distance than the absolute value of `gapSize` property,
  4603. * which will result in disappearing graph completely. To prevent
  4604. * this situation the mentioned distance between grouped points is
  4605. * used instead of previously defined `gapSize`.
  4606. *
  4607. * In practice, this option is most often used to visualize gaps in
  4608. * time series. In a stock chart, intraday data is available for
  4609. * daytime hours, while gaps will appear in nights and weekends.
  4610. *
  4611. * @see [gapUnit](plotOptions.series.gapUnit)
  4612. * @see [xAxis.breaks](#xAxis.breaks)
  4613. *
  4614. * @sample {highstock} stock/plotoptions/series-gapsize/
  4615. * Setting the gap size to 2 introduces gaps for weekends
  4616. * in daily datasets.
  4617. *
  4618. * @type {number}
  4619. * @default 0
  4620. * @product highstock
  4621. * @requires modules/broken-axis
  4622. * @apioption plotOptions.series.gapSize
  4623. */
  4624. /**
  4625. * Together with [gapSize](plotOptions.series.gapSize), this option
  4626. * defines where to draw gaps in the graph.
  4627. *
  4628. * When the `gapUnit` is `"relative"` (default), a gap size of 5
  4629. * means that if the distance between two points is greater than
  4630. * 5 times that of the two closest points, the graph will be broken.
  4631. *
  4632. * When the `gapUnit` is `"value"`, the gap is based on absolute
  4633. * axis values, which on a datetime axis is milliseconds. This also
  4634. * applies to the navigator series that inherits gap options from
  4635. * the base series.
  4636. *
  4637. * @see [gapSize](plotOptions.series.gapSize)
  4638. *
  4639. * @type {string}
  4640. * @default relative
  4641. * @since 5.0.13
  4642. * @product highstock
  4643. * @validvalue ["relative", "value"]
  4644. * @requires modules/broken-axis
  4645. * @apioption plotOptions.series.gapUnit
  4646. */
  4647. if (gapSize && i > 0) { // #5008
  4648. // Gap unit is relative
  4649. if (this.options.gapUnit !== 'value') {
  4650. gapSize *= this.basePointRange;
  4651. }
  4652. // Setting a new gapSize in case dataGrouping is enabled (#7686)
  4653. if (groupingSize &&
  4654. groupingSize > gapSize &&
  4655. // Except when DG is forced (e.g. from other series)
  4656. // and has lower granularity than actual points (#11351)
  4657. groupingSize >= this.basePointRange) {
  4658. gapSize = groupingSize;
  4659. }
  4660. // extension for ordinal breaks
  4661. var current = void 0,
  4662. next = void 0;
  4663. while (i--) {
  4664. // Reassign next if it is not visible
  4665. if (!(next && next.visible !== false)) {
  4666. next = points[i + 1];
  4667. }
  4668. current = points[i];
  4669. // Skip iteration if one of the points is not visible
  4670. if (next.visible === false || current.visible === false) {
  4671. continue;
  4672. }
  4673. if (next.x - current.x > gapSize) {
  4674. var xRange = (current.x + next.x) / 2;
  4675. points.splice(// insert after this one
  4676. i + 1, 0, {
  4677. isNull: true,
  4678. x: xRange
  4679. });
  4680. // For stacked chart generate empty stack items, #6546
  4681. if (yAxis.stacking && this.options.stacking) {
  4682. stack = yAxis.stacking.stacks[this.stackKey][xRange] =
  4683. new StackItem(yAxis, yAxis.options
  4684. .stackLabels, false, xRange, this.stack);
  4685. stack.total = 0;
  4686. }
  4687. }
  4688. // Assign current to next for the upcoming iteration
  4689. next = current;
  4690. }
  4691. }
  4692. // Call base method
  4693. return this.getGraphPath(points);
  4694. };
  4695. /* eslint-disable no-invalid-this */
  4696. addEvent(AxisClass, 'init', function () {
  4697. var axis = this;
  4698. if (!axis.brokenAxis) {
  4699. axis.brokenAxis = new BrokenAxisAdditions(axis);
  4700. }
  4701. });
  4702. addEvent(AxisClass, 'afterInit', function () {
  4703. if (typeof this.brokenAxis !== 'undefined') {
  4704. this.brokenAxis.setBreaks(this.options.breaks, false);
  4705. }
  4706. });
  4707. addEvent(AxisClass, 'afterSetTickPositions', function () {
  4708. var axis = this;
  4709. var brokenAxis = axis.brokenAxis;
  4710. if (brokenAxis &&
  4711. brokenAxis.hasBreaks) {
  4712. var tickPositions = this.tickPositions,
  4713. info = this.tickPositions.info,
  4714. newPositions = [],
  4715. i;
  4716. for (i = 0; i < tickPositions.length; i++) {
  4717. if (!brokenAxis.isInAnyBreak(tickPositions[i])) {
  4718. newPositions.push(tickPositions[i]);
  4719. }
  4720. }
  4721. this.tickPositions = newPositions;
  4722. this.tickPositions.info = info;
  4723. }
  4724. });
  4725. // Force Axis to be not-ordinal when breaks are defined
  4726. addEvent(AxisClass, 'afterSetOptions', function () {
  4727. if (this.brokenAxis && this.brokenAxis.hasBreaks) {
  4728. this.options.ordinal = false;
  4729. }
  4730. });
  4731. addEvent(SeriesClass, 'afterGeneratePoints', function () {
  4732. var _a = this,
  4733. isDirty = _a.isDirty,
  4734. connectNulls = _a.options.connectNulls,
  4735. points = _a.points,
  4736. xAxis = _a.xAxis,
  4737. yAxis = _a.yAxis;
  4738. // Set, or reset visibility of the points. Axis.setBreaks marks the
  4739. // series as isDirty
  4740. if (isDirty) {
  4741. var i = points.length;
  4742. while (i--) {
  4743. var point = points[i];
  4744. // Respect nulls inside the break (#4275)
  4745. var nullGap = point.y === null && connectNulls === false;
  4746. var isPointInBreak = (!nullGap && ((xAxis &&
  4747. xAxis.brokenAxis &&
  4748. xAxis.brokenAxis.isInAnyBreak(point.x,
  4749. true)) || (yAxis &&
  4750. yAxis.brokenAxis &&
  4751. yAxis.brokenAxis.isInAnyBreak(point.y,
  4752. true))));
  4753. // Set point.visible if in any break.
  4754. // If not in break, reset visible to original value.
  4755. point.visible = isPointInBreak ?
  4756. false :
  4757. point.options.visible !== false;
  4758. }
  4759. }
  4760. });
  4761. addEvent(SeriesClass, 'afterRender', function drawPointsWrapped() {
  4762. this.drawBreaks(this.xAxis, ['x']);
  4763. this.drawBreaks(this.yAxis, pick(this.pointArrayMap, ['y']));
  4764. });
  4765. };
  4766. return BrokenAxis;
  4767. }());
  4768. BrokenAxis.compose(Axis, Series); // @todo remove automatism
  4769. return BrokenAxis;
  4770. });
  4771. _registerModule(_modules, 'masters/modules/broken-axis.src.js', [], function () {
  4772. });
  4773. _registerModule(_modules, 'Extensions/DataGrouping.js', [_modules['Core/Axis/DateTimeAxis.js'], _modules['Core/Globals.js'], _modules['Core/Options.js'], _modules['Core/Series/Point.js'], _modules['Core/Tooltip.js'], _modules['Core/Utilities.js']], function (DateTimeAxis, H, O, Point, Tooltip, U) {
  4774. /* *
  4775. *
  4776. * (c) 2010-2020 Torstein Honsi
  4777. *
  4778. * License: www.highcharts.com/license
  4779. *
  4780. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  4781. *
  4782. * */
  4783. /**
  4784. * @typedef {"average"|"averages"|"open"|"high"|"low"|"close"|"sum"} Highcharts.DataGroupingApproximationValue
  4785. */
  4786. /**
  4787. * @interface Highcharts.DataGroupingInfoObject
  4788. */ /**
  4789. * @name Highcharts.DataGroupingInfoObject#length
  4790. * @type {number}
  4791. */ /**
  4792. * @name Highcharts.DataGroupingInfoObject#options
  4793. * @type {Highcharts.SeriesOptionsType|undefined}
  4794. */ /**
  4795. * @name Highcharts.DataGroupingInfoObject#start
  4796. * @type {number}
  4797. */
  4798. ''; // detach doclets above
  4799. var defaultOptions = O.defaultOptions;
  4800. var addEvent = U.addEvent,
  4801. arrayMax = U.arrayMax,
  4802. arrayMin = U.arrayMin,
  4803. correctFloat = U.correctFloat,
  4804. defined = U.defined,
  4805. error = U.error,
  4806. extend = U.extend,
  4807. format = U.format,
  4808. isNumber = U.isNumber,
  4809. merge = U.merge,
  4810. pick = U.pick;
  4811. var Axis = H.Axis,
  4812. Series = H.Series;
  4813. /* ************************************************************************** *
  4814. * Start data grouping module *
  4815. * ************************************************************************** */
  4816. /* eslint-disable no-invalid-this, valid-jsdoc */
  4817. /**
  4818. * Define the available approximation types. The data grouping
  4819. * approximations takes an array or numbers as the first parameter. In case
  4820. * of ohlc, four arrays are sent in as four parameters. Each array consists
  4821. * only of numbers. In case null values belong to the group, the property
  4822. * .hasNulls will be set to true on the array.
  4823. *
  4824. * @product highstock
  4825. *
  4826. * @private
  4827. * @name Highcharts.approximations
  4828. * @type {Highcharts.Dictionary<Function>}
  4829. */
  4830. var approximations = H.approximations = {
  4831. sum: function (arr) {
  4832. var len = arr.length,
  4833. ret;
  4834. // 1. it consists of nulls exclusive
  4835. if (!len && arr.hasNulls) {
  4836. ret = null;
  4837. // 2. it has a length and real values
  4838. }
  4839. else if (len) {
  4840. ret = 0;
  4841. while (len--) {
  4842. ret += arr[len];
  4843. }
  4844. }
  4845. // 3. it has zero length, so just return undefined
  4846. // => doNothing()
  4847. return ret;
  4848. },
  4849. average: function (arr) {
  4850. var len = arr.length,
  4851. ret = approximations.sum(arr);
  4852. // If we have a number, return it divided by the length. If not,
  4853. // return null or undefined based on what the sum method finds.
  4854. if (isNumber(ret) && len) {
  4855. ret = correctFloat(ret / len);
  4856. }
  4857. return ret;
  4858. },
  4859. // The same as average, but for series with multiple values, like area
  4860. // ranges.
  4861. averages: function () {
  4862. var ret = [];
  4863. [].forEach.call(arguments, function (arr) {
  4864. ret.push(approximations.average(arr));
  4865. });
  4866. // Return undefined when first elem. is undefined and let
  4867. // sum method handle null (#7377)
  4868. return typeof ret[0] === 'undefined' ? void 0 : ret;
  4869. },
  4870. open: function (arr) {
  4871. return arr.length ? arr[0] : (arr.hasNulls ? null : void 0);
  4872. },
  4873. high: function (arr) {
  4874. return arr.length ?
  4875. arrayMax(arr) :
  4876. (arr.hasNulls ? null : void 0);
  4877. },
  4878. low: function (arr) {
  4879. return arr.length ?
  4880. arrayMin(arr) :
  4881. (arr.hasNulls ? null : void 0);
  4882. },
  4883. close: function (arr) {
  4884. return arr.length ?
  4885. arr[arr.length - 1] :
  4886. (arr.hasNulls ? null : void 0);
  4887. },
  4888. // ohlc and range are special cases where a multidimensional array is
  4889. // input and an array is output
  4890. ohlc: function (open, high, low, close) {
  4891. open = approximations.open(open);
  4892. high = approximations.high(high);
  4893. low = approximations.low(low);
  4894. close = approximations.close(close);
  4895. if (isNumber(open) ||
  4896. isNumber(high) ||
  4897. isNumber(low) ||
  4898. isNumber(close)) {
  4899. return [open, high, low, close];
  4900. }
  4901. // else, return is undefined
  4902. },
  4903. range: function (low, high) {
  4904. low = approximations.low(low);
  4905. high = approximations.high(high);
  4906. if (isNumber(low) || isNumber(high)) {
  4907. return [low, high];
  4908. }
  4909. if (low === null && high === null) {
  4910. return null;
  4911. }
  4912. // else, return is undefined
  4913. }
  4914. };
  4915. var groupData = function (xData,
  4916. yData,
  4917. groupPositions,
  4918. approximation) {
  4919. var series = this,
  4920. data = series.data,
  4921. dataOptions = series.options && series.options.data,
  4922. groupedXData = [],
  4923. groupedYData = [],
  4924. groupMap = [],
  4925. dataLength = xData.length,
  4926. pointX,
  4927. pointY,
  4928. groupedY,
  4929. // when grouping the fake extended axis for panning,
  4930. // we don't need to consider y
  4931. handleYData = !!yData,
  4932. values = [],
  4933. approximationFn,
  4934. pointArrayMap = series.pointArrayMap,
  4935. pointArrayMapLength = pointArrayMap && pointArrayMap.length,
  4936. extendedPointArrayMap = ['x'].concat(pointArrayMap || ['y']),
  4937. pos = 0,
  4938. start = 0,
  4939. valuesLen,
  4940. i,
  4941. j;
  4942. /**
  4943. * @private
  4944. */
  4945. function getApproximation(approx) {
  4946. if (typeof approx === 'function') {
  4947. return approx;
  4948. }
  4949. if (approximations[approx]) {
  4950. return approximations[approx];
  4951. }
  4952. return approximations[(series.getDGApproximation && series.getDGApproximation()) ||
  4953. 'average'];
  4954. }
  4955. approximationFn = getApproximation(approximation);
  4956. // Calculate values array size from pointArrayMap length
  4957. if (pointArrayMapLength) {
  4958. pointArrayMap.forEach(function () {
  4959. values.push([]);
  4960. });
  4961. }
  4962. else {
  4963. values.push([]);
  4964. }
  4965. valuesLen = pointArrayMapLength || 1;
  4966. // Start with the first point within the X axis range (#2696)
  4967. for (i = 0; i <= dataLength; i++) {
  4968. if (xData[i] >= groupPositions[0]) {
  4969. break;
  4970. }
  4971. }
  4972. for (i; i <= dataLength; i++) {
  4973. // when a new group is entered, summarize and initialize
  4974. // the previous group
  4975. while ((typeof groupPositions[pos + 1] !== 'undefined' &&
  4976. xData[i] >= groupPositions[pos + 1]) ||
  4977. i === dataLength) { // get the last group
  4978. // get group x and y
  4979. pointX = groupPositions[pos];
  4980. series.dataGroupInfo = {
  4981. start: series.cropStart + start,
  4982. length: values[0].length
  4983. };
  4984. groupedY = approximationFn.apply(series, values);
  4985. // By default, let options of the first grouped point be passed over
  4986. // to the grouped point. This allows preserving properties like
  4987. // `name` and `color` or custom properties. Implementers can
  4988. // override this from the approximation function, where they can
  4989. // write custom options to `this.dataGroupInfo.options`.
  4990. if (series.pointClass && !defined(series.dataGroupInfo.options)) {
  4991. // Convert numbers and arrays into objects
  4992. series.dataGroupInfo.options = merge(series.pointClass.prototype
  4993. .optionsToObject.call({ series: series }, series.options.data[series.cropStart + start]));
  4994. // Make sure the raw data (x, y, open, high etc) is not copied
  4995. // over and overwriting approximated data.
  4996. extendedPointArrayMap.forEach(function (key) {
  4997. delete series.dataGroupInfo.options[key];
  4998. });
  4999. }
  5000. // push the grouped data
  5001. if (typeof groupedY !== 'undefined') {
  5002. groupedXData.push(pointX);
  5003. groupedYData.push(groupedY);
  5004. groupMap.push(series.dataGroupInfo);
  5005. }
  5006. // reset the aggregate arrays
  5007. start = i;
  5008. for (j = 0; j < valuesLen; j++) {
  5009. values[j].length = 0; // faster than values[j] = []
  5010. values[j].hasNulls = false;
  5011. }
  5012. // Advance on the group positions
  5013. pos += 1;
  5014. // don't loop beyond the last group
  5015. if (i === dataLength) {
  5016. break;
  5017. }
  5018. }
  5019. // break out
  5020. if (i === dataLength) {
  5021. break;
  5022. }
  5023. // for each raw data point, push it to an array that contains all values
  5024. // for this specific group
  5025. if (pointArrayMap) {
  5026. var index = series.cropStart + i,
  5027. point = (data && data[index]) ||
  5028. series.pointClass.prototype.applyOptions.apply({
  5029. series: series
  5030. },
  5031. [dataOptions[index]]),
  5032. val;
  5033. for (j = 0; j < pointArrayMapLength; j++) {
  5034. val = point[pointArrayMap[j]];
  5035. if (isNumber(val)) {
  5036. values[j].push(val);
  5037. }
  5038. else if (val === null) {
  5039. values[j].hasNulls = true;
  5040. }
  5041. }
  5042. }
  5043. else {
  5044. pointY = handleYData ? yData[i] : null;
  5045. if (isNumber(pointY)) {
  5046. values[0].push(pointY);
  5047. }
  5048. else if (pointY === null) {
  5049. values[0].hasNulls = true;
  5050. }
  5051. }
  5052. }
  5053. return {
  5054. groupedXData: groupedXData,
  5055. groupedYData: groupedYData,
  5056. groupMap: groupMap
  5057. };
  5058. };
  5059. var dataGrouping = {
  5060. approximations: approximations,
  5061. groupData: groupData
  5062. };
  5063. // -----------------------------------------------------------------------------
  5064. // The following code applies to implementation of data grouping on a Series
  5065. var seriesProto = Series.prototype, baseProcessData = seriesProto.processData, baseGeneratePoints = seriesProto.generatePoints,
  5066. /** @ignore */
  5067. commonOptions = {
  5068. // enabled: null, // (true for stock charts, false for basic),
  5069. // forced: undefined,
  5070. groupPixelWidth: 2,
  5071. // the first one is the point or start value, the second is the start
  5072. // value if we're dealing with range, the third one is the end value if
  5073. // dealing with a range
  5074. dateTimeLabelFormats: {
  5075. millisecond: [
  5076. '%A, %b %e, %H:%M:%S.%L',
  5077. '%A, %b %e, %H:%M:%S.%L',
  5078. '-%H:%M:%S.%L'
  5079. ],
  5080. second: [
  5081. '%A, %b %e, %H:%M:%S',
  5082. '%A, %b %e, %H:%M:%S',
  5083. '-%H:%M:%S'
  5084. ],
  5085. minute: [
  5086. '%A, %b %e, %H:%M',
  5087. '%A, %b %e, %H:%M',
  5088. '-%H:%M'
  5089. ],
  5090. hour: [
  5091. '%A, %b %e, %H:%M',
  5092. '%A, %b %e, %H:%M',
  5093. '-%H:%M'
  5094. ],
  5095. day: [
  5096. '%A, %b %e, %Y',
  5097. '%A, %b %e',
  5098. '-%A, %b %e, %Y'
  5099. ],
  5100. week: [
  5101. 'Week from %A, %b %e, %Y',
  5102. '%A, %b %e',
  5103. '-%A, %b %e, %Y'
  5104. ],
  5105. month: [
  5106. '%B %Y',
  5107. '%B',
  5108. '-%B %Y'
  5109. ],
  5110. year: [
  5111. '%Y',
  5112. '%Y',
  5113. '-%Y'
  5114. ]
  5115. }
  5116. // smoothed = false, // enable this for navigator series only
  5117. }, specificOptions = {
  5118. line: {},
  5119. spline: {},
  5120. area: {},
  5121. areaspline: {},
  5122. arearange: {},
  5123. column: {
  5124. groupPixelWidth: 10
  5125. },
  5126. columnrange: {
  5127. groupPixelWidth: 10
  5128. },
  5129. candlestick: {
  5130. groupPixelWidth: 10
  5131. },
  5132. ohlc: {
  5133. groupPixelWidth: 5
  5134. }
  5135. },
  5136. // units are defined in a separate array to allow complete overriding in
  5137. // case of a user option
  5138. defaultDataGroupingUnits = H.defaultDataGroupingUnits = [
  5139. [
  5140. 'millisecond',
  5141. [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
  5142. ], [
  5143. 'second',
  5144. [1, 2, 5, 10, 15, 30]
  5145. ], [
  5146. 'minute',
  5147. [1, 2, 5, 10, 15, 30]
  5148. ], [
  5149. 'hour',
  5150. [1, 2, 3, 4, 6, 8, 12]
  5151. ], [
  5152. 'day',
  5153. [1]
  5154. ], [
  5155. 'week',
  5156. [1]
  5157. ], [
  5158. 'month',
  5159. [1, 3, 6]
  5160. ], [
  5161. 'year',
  5162. null
  5163. ]
  5164. ];
  5165. // Set default approximations to the prototypes if present. Properties are
  5166. // inherited down. Can be overridden for individual series types.
  5167. seriesProto.getDGApproximation = function () {
  5168. if (this.is('arearange')) {
  5169. return 'range';
  5170. }
  5171. if (this.is('ohlc')) {
  5172. return 'ohlc';
  5173. }
  5174. if (this.is('column')) {
  5175. return 'sum';
  5176. }
  5177. return 'average';
  5178. };
  5179. /**
  5180. * Takes parallel arrays of x and y data and groups the data into intervals
  5181. * defined by groupPositions, a collection of starting x values for each group.
  5182. *
  5183. * @private
  5184. * @function Highcharts.Series#groupData
  5185. *
  5186. * @param {Array<number>} xData
  5187. *
  5188. * @param {Array<number>|Array<Array<number>>} yData
  5189. *
  5190. * @param {boolean} groupPositions
  5191. *
  5192. * @param {string|Function} approximation
  5193. *
  5194. * @return {void}
  5195. */
  5196. seriesProto.groupData = groupData;
  5197. // Extend the basic processData method, that crops the data to the current zoom
  5198. // range, with data grouping logic.
  5199. seriesProto.processData = function () {
  5200. var series = this,
  5201. chart = series.chart,
  5202. options = series.options,
  5203. dataGroupingOptions = options.dataGrouping,
  5204. groupingEnabled = series.allowDG !== false && dataGroupingOptions &&
  5205. pick(dataGroupingOptions.enabled,
  5206. chart.options.isStock),
  5207. visible = (series.visible || !chart.options.chart.ignoreHiddenSeries),
  5208. hasGroupedData,
  5209. skip,
  5210. lastDataGrouping = this.currentDataGrouping,
  5211. currentDataGrouping,
  5212. croppedData,
  5213. revertRequireSorting = false;
  5214. // Run base method
  5215. series.forceCrop = groupingEnabled; // #334
  5216. series.groupPixelWidth = null; // #2110
  5217. series.hasProcessed = true; // #2692
  5218. // Data needs to be sorted for dataGrouping
  5219. if (groupingEnabled && !series.requireSorting) {
  5220. series.requireSorting = revertRequireSorting = true;
  5221. }
  5222. // Skip if processData returns false or if grouping is disabled (in that
  5223. // order)
  5224. skip = (baseProcessData.apply(series, arguments) === false ||
  5225. !groupingEnabled);
  5226. // Revert original requireSorting value if changed
  5227. if (revertRequireSorting) {
  5228. series.requireSorting = false;
  5229. }
  5230. if (!skip) {
  5231. series.destroyGroupedData();
  5232. var i,
  5233. processedXData = dataGroupingOptions.groupAll ?
  5234. series.xData :
  5235. series.processedXData,
  5236. processedYData = dataGroupingOptions.groupAll ?
  5237. series.yData :
  5238. series.processedYData,
  5239. plotSizeX = chart.plotSizeX,
  5240. xAxis = series.xAxis,
  5241. ordinal = xAxis.options.ordinal,
  5242. groupPixelWidth = series.groupPixelWidth =
  5243. xAxis.getGroupPixelWidth && xAxis.getGroupPixelWidth();
  5244. // Execute grouping if the amount of points is greater than the limit
  5245. // defined in groupPixelWidth
  5246. if (groupPixelWidth) {
  5247. hasGroupedData = true;
  5248. // Force recreation of point instances in series.translate, #5699
  5249. series.isDirty = true;
  5250. series.points = null; // #6709
  5251. var extremes = xAxis.getExtremes(),
  5252. xMin = extremes.min,
  5253. xMax = extremes.max,
  5254. groupIntervalFactor = (ordinal &&
  5255. xAxis.ordinal &&
  5256. xAxis.ordinal.getGroupIntervalFactor(xMin,
  5257. xMax,
  5258. series)) || 1,
  5259. interval = (groupPixelWidth * (xMax - xMin) / plotSizeX) *
  5260. groupIntervalFactor,
  5261. groupPositions = xAxis.getTimeTicks(DateTimeAxis.AdditionsClass.prototype.normalizeTimeTickInterval(interval,
  5262. dataGroupingOptions.units ||
  5263. defaultDataGroupingUnits),
  5264. // Processed data may extend beyond axis (#4907)
  5265. Math.min(xMin,
  5266. processedXData[0]),
  5267. Math.max(xMax,
  5268. processedXData[processedXData.length - 1]),
  5269. xAxis.options.startOfWeek,
  5270. processedXData,
  5271. series.closestPointRange),
  5272. groupedData = seriesProto.groupData.apply(series,
  5273. [
  5274. processedXData,
  5275. processedYData,
  5276. groupPositions,
  5277. dataGroupingOptions.approximation
  5278. ]),
  5279. groupedXData = groupedData.groupedXData,
  5280. groupedYData = groupedData.groupedYData,
  5281. gapSize = 0;
  5282. // Prevent the smoothed data to spill out left and right, and make
  5283. // sure data is not shifted to the left
  5284. if (dataGroupingOptions.smoothed && groupedXData.length) {
  5285. i = groupedXData.length - 1;
  5286. groupedXData[i] = Math.min(groupedXData[i], xMax);
  5287. while (i-- && i > 0) {
  5288. groupedXData[i] += interval / 2;
  5289. }
  5290. groupedXData[0] = Math.max(groupedXData[0], xMin);
  5291. }
  5292. // Record what data grouping values were used
  5293. for (i = 1; i < groupPositions.length; i++) {
  5294. // The grouped gapSize needs to be the largest distance between
  5295. // the group to capture varying group sizes like months or DST
  5296. // crossing (#10000). Also check that the gap is not at the
  5297. // start of a segment.
  5298. if (!groupPositions.info.segmentStarts ||
  5299. groupPositions.info.segmentStarts.indexOf(i) === -1) {
  5300. gapSize = Math.max(groupPositions[i] - groupPositions[i - 1], gapSize);
  5301. }
  5302. }
  5303. currentDataGrouping = groupPositions.info;
  5304. currentDataGrouping.gapSize = gapSize;
  5305. series.closestPointRange = groupPositions.info.totalRange;
  5306. series.groupMap = groupedData.groupMap;
  5307. // Make sure the X axis extends to show the first group (#2533)
  5308. // But only for visible series (#5493, #6393)
  5309. if (defined(groupedXData[0]) &&
  5310. groupedXData[0] < xAxis.min &&
  5311. visible) {
  5312. if ((!defined(xAxis.options.min) &&
  5313. xAxis.min <= xAxis.dataMin) ||
  5314. xAxis.min === xAxis.dataMin) {
  5315. xAxis.min = Math.min(groupedXData[0], xAxis.min);
  5316. }
  5317. xAxis.dataMin = Math.min(groupedXData[0], xAxis.dataMin);
  5318. }
  5319. // We calculated all group positions but we should render
  5320. // only the ones within the visible range
  5321. if (dataGroupingOptions.groupAll) {
  5322. croppedData = series.cropData(groupedXData, groupedYData, xAxis.min, xAxis.max, 1 // Ordinal xAxis will remove left-most points otherwise
  5323. );
  5324. groupedXData = croppedData.xData;
  5325. groupedYData = croppedData.yData;
  5326. }
  5327. // Set series props
  5328. series.processedXData = groupedXData;
  5329. series.processedYData = groupedYData;
  5330. }
  5331. else {
  5332. series.groupMap = null;
  5333. }
  5334. series.hasGroupedData = hasGroupedData;
  5335. series.currentDataGrouping = currentDataGrouping;
  5336. series.preventGraphAnimation =
  5337. (lastDataGrouping && lastDataGrouping.totalRange) !==
  5338. (currentDataGrouping && currentDataGrouping.totalRange);
  5339. }
  5340. };
  5341. // Destroy the grouped data points. #622, #740
  5342. seriesProto.destroyGroupedData = function () {
  5343. // Clear previous groups
  5344. if (this.groupedData) {
  5345. this.groupedData.forEach(function (point, i) {
  5346. if (point) {
  5347. this.groupedData[i] = point.destroy ?
  5348. point.destroy() : null;
  5349. }
  5350. }, this);
  5351. // Clears all:
  5352. // - `this.groupedData`
  5353. // - `this.points`
  5354. // - `preserve` object in series.update()
  5355. this.groupedData.length = 0;
  5356. }
  5357. };
  5358. // Override the generatePoints method by adding a reference to grouped data
  5359. seriesProto.generatePoints = function () {
  5360. baseGeneratePoints.apply(this);
  5361. // Record grouped data in order to let it be destroyed the next time
  5362. // processData runs
  5363. this.destroyGroupedData(); // #622
  5364. this.groupedData = this.hasGroupedData ? this.points : null;
  5365. };
  5366. // Override point prototype to throw a warning when trying to update grouped
  5367. // points.
  5368. addEvent(Point, 'update', function () {
  5369. if (this.dataGroup) {
  5370. error(24, false, this.series.chart);
  5371. return false;
  5372. }
  5373. });
  5374. // Extend the original method, make the tooltip's header reflect the grouped
  5375. // range.
  5376. addEvent(Tooltip, 'headerFormatter', function (e) {
  5377. var tooltip = this,
  5378. chart = this.chart,
  5379. time = chart.time,
  5380. labelConfig = e.labelConfig,
  5381. series = labelConfig.series,
  5382. options = series.options,
  5383. tooltipOptions = series.tooltipOptions,
  5384. dataGroupingOptions = options.dataGrouping,
  5385. xDateFormat = tooltipOptions.xDateFormat,
  5386. xDateFormatEnd,
  5387. xAxis = series.xAxis,
  5388. currentDataGrouping,
  5389. dateTimeLabelFormats,
  5390. labelFormats,
  5391. formattedKey,
  5392. formatString = tooltipOptions[(e.isFooter ? 'footer' : 'header') + 'Format'];
  5393. // apply only to grouped series
  5394. if (xAxis &&
  5395. xAxis.options.type === 'datetime' &&
  5396. dataGroupingOptions &&
  5397. isNumber(labelConfig.key)) {
  5398. // set variables
  5399. currentDataGrouping = series.currentDataGrouping;
  5400. dateTimeLabelFormats = dataGroupingOptions.dateTimeLabelFormats ||
  5401. // Fallback to commonOptions (#9693)
  5402. commonOptions.dateTimeLabelFormats;
  5403. // if we have grouped data, use the grouping information to get the
  5404. // right format
  5405. if (currentDataGrouping) {
  5406. labelFormats =
  5407. dateTimeLabelFormats[currentDataGrouping.unitName];
  5408. if (currentDataGrouping.count === 1) {
  5409. xDateFormat = labelFormats[0];
  5410. }
  5411. else {
  5412. xDateFormat = labelFormats[1];
  5413. xDateFormatEnd = labelFormats[2];
  5414. }
  5415. // if not grouped, and we don't have set the xDateFormat option, get the
  5416. // best fit, so if the least distance between points is one minute, show
  5417. // it, but if the least distance is one day, skip hours and minutes etc.
  5418. }
  5419. else if (!xDateFormat && dateTimeLabelFormats) {
  5420. xDateFormat = tooltip.getXDateFormat(labelConfig, tooltipOptions, xAxis);
  5421. }
  5422. // now format the key
  5423. formattedKey = time.dateFormat(xDateFormat, labelConfig.key);
  5424. if (xDateFormatEnd) {
  5425. formattedKey += time.dateFormat(xDateFormatEnd, labelConfig.key + currentDataGrouping.totalRange - 1);
  5426. }
  5427. // Replace default header style with class name
  5428. if (series.chart.styledMode) {
  5429. formatString = this.styledModeFormat(formatString);
  5430. }
  5431. // return the replaced format
  5432. e.text = format(formatString, {
  5433. point: extend(labelConfig.point, { key: formattedKey }),
  5434. series: series
  5435. }, chart);
  5436. e.preventDefault();
  5437. }
  5438. });
  5439. // Destroy grouped data on series destroy
  5440. addEvent(Series, 'destroy', seriesProto.destroyGroupedData);
  5441. // Handle default options for data grouping. This must be set at runtime because
  5442. // some series types are defined after this.
  5443. addEvent(Series, 'afterSetOptions', function (e) {
  5444. var options = e.options,
  5445. type = this.type,
  5446. plotOptions = this.chart.options.plotOptions,
  5447. defaultOptions = O.defaultOptions.plotOptions[type].dataGrouping,
  5448. // External series, for example technical indicators should also
  5449. // inherit commonOptions which are not available outside this module
  5450. baseOptions = this.useCommonDataGrouping && commonOptions;
  5451. if (specificOptions[type] || baseOptions) { // #1284
  5452. if (!defaultOptions) {
  5453. defaultOptions = merge(commonOptions, specificOptions[type]);
  5454. }
  5455. options.dataGrouping = merge(baseOptions, defaultOptions, plotOptions.series && plotOptions.series.dataGrouping, // #1228
  5456. // Set by the StockChart constructor:
  5457. plotOptions[type].dataGrouping, this.userOptions.dataGrouping);
  5458. }
  5459. });
  5460. // When resetting the scale reset the hasProccessed flag to avoid taking
  5461. // previous data grouping of neighbour series into accound when determining
  5462. // group pixel width (#2692).
  5463. addEvent(Axis, 'afterSetScale', function () {
  5464. this.series.forEach(function (series) {
  5465. series.hasProcessed = false;
  5466. });
  5467. });
  5468. // Get the data grouping pixel width based on the greatest defined individual
  5469. // width of the axis' series, and if whether one of the axes need grouping.
  5470. Axis.prototype.getGroupPixelWidth = function () {
  5471. var series = this.series,
  5472. len = series.length,
  5473. i,
  5474. groupPixelWidth = 0,
  5475. doGrouping = false,
  5476. dataLength,
  5477. dgOptions;
  5478. // If multiple series are compared on the same x axis, give them the same
  5479. // group pixel width (#334)
  5480. i = len;
  5481. while (i--) {
  5482. dgOptions = series[i].options.dataGrouping;
  5483. if (dgOptions) {
  5484. groupPixelWidth = Math.max(groupPixelWidth,
  5485. // Fallback to commonOptions (#9693)
  5486. pick(dgOptions.groupPixelWidth, commonOptions.groupPixelWidth));
  5487. }
  5488. }
  5489. // If one of the series needs grouping, apply it to all (#1634)
  5490. i = len;
  5491. while (i--) {
  5492. dgOptions = series[i].options.dataGrouping;
  5493. if (dgOptions && series[i].hasProcessed) { // #2692
  5494. dataLength = (series[i].processedXData || series[i].data).length;
  5495. // Execute grouping if the amount of points is greater than the
  5496. // limit defined in groupPixelWidth
  5497. if (series[i].groupPixelWidth ||
  5498. dataLength >
  5499. (this.chart.plotSizeX / groupPixelWidth) ||
  5500. (dataLength && dgOptions.forced)) {
  5501. doGrouping = true;
  5502. }
  5503. }
  5504. }
  5505. return doGrouping ? groupPixelWidth : 0;
  5506. };
  5507. /**
  5508. * Highstock only. Force data grouping on all the axis' series.
  5509. *
  5510. * @product highstock
  5511. *
  5512. * @function Highcharts.Axis#setDataGrouping
  5513. *
  5514. * @param {boolean|Highcharts.DataGroupingOptionsObject} [dataGrouping]
  5515. * A `dataGrouping` configuration. Use `false` to disable data grouping
  5516. * dynamically.
  5517. *
  5518. * @param {boolean} [redraw=true]
  5519. * Whether to redraw the chart or wait for a later call to
  5520. * {@link Chart#redraw}.
  5521. *
  5522. * @return {void}
  5523. */
  5524. Axis.prototype.setDataGrouping = function (dataGrouping, redraw) {
  5525. var axis = this;
  5526. var i;
  5527. redraw = pick(redraw, true);
  5528. if (!dataGrouping) {
  5529. dataGrouping = {
  5530. forced: false,
  5531. units: null
  5532. };
  5533. }
  5534. // Axis is instantiated, update all series
  5535. if (this instanceof Axis) {
  5536. i = this.series.length;
  5537. while (i--) {
  5538. this.series[i].update({
  5539. dataGrouping: dataGrouping
  5540. }, false);
  5541. }
  5542. // Axis not yet instanciated, alter series options
  5543. }
  5544. else {
  5545. this.chart.options.series.forEach(function (seriesOptions) {
  5546. seriesOptions.dataGrouping = dataGrouping;
  5547. }, false);
  5548. }
  5549. // Clear ordinal slope, so we won't accidentaly use the old one (#7827)
  5550. if (axis.ordinal) {
  5551. axis.ordinal.slope = void 0;
  5552. }
  5553. if (redraw) {
  5554. this.chart.redraw();
  5555. }
  5556. };
  5557. H.dataGrouping = dataGrouping;
  5558. /* eslint-enable no-invalid-this, valid-jsdoc */
  5559. /**
  5560. * Data grouping is the concept of sampling the data values into larger
  5561. * blocks in order to ease readability and increase performance of the
  5562. * JavaScript charts. Highstock by default applies data grouping when
  5563. * the points become closer than a certain pixel value, determined by
  5564. * the `groupPixelWidth` option.
  5565. *
  5566. * If data grouping is applied, the grouping information of grouped
  5567. * points can be read from the [Point.dataGroup](
  5568. * /class-reference/Highcharts.Point#dataGroup). If point options other than
  5569. * the data itself are set, for example `name` or `color` or custom properties,
  5570. * the grouping logic doesn't know how to group it. In this case the options of
  5571. * the first point instance are copied over to the group point. This can be
  5572. * altered through a custom `approximation` callback function.
  5573. *
  5574. * @declare Highcharts.DataGroupingOptionsObject
  5575. * @product highstock
  5576. * @requires product:highstock
  5577. * @requires module:modules/datagrouping
  5578. * @apioption plotOptions.series.dataGrouping
  5579. */
  5580. /**
  5581. * The method of approximation inside a group. When for example 30 days
  5582. * are grouped into one month, this determines what value should represent
  5583. * the group. Possible values are "average", "averages", "open", "high",
  5584. * "low", "close" and "sum". For OHLC and candlestick series the approximation
  5585. * is "ohlc" by default, which finds the open, high, low and close values
  5586. * within all the grouped data. For ranges, the approximation is "range",
  5587. * which finds the low and high values. For multi-dimensional data,
  5588. * like ranges and OHLC, "averages" will compute the average for each
  5589. * dimension.
  5590. *
  5591. * Custom aggregate methods can be added by assigning a callback function
  5592. * as the approximation. This function takes a numeric array as the
  5593. * argument and should return a single numeric value or `null`. Note
  5594. * that the numeric array will never contain null values, only true
  5595. * numbers. Instead, if null values are present in the raw data, the
  5596. * numeric array will have an `.hasNulls` property set to `true`. For
  5597. * single-value data sets the data is available in the first argument
  5598. * of the callback function. For OHLC data sets, all the open values
  5599. * are in the first argument, all high values in the second etc.
  5600. *
  5601. * Since v4.2.7, grouping meta data is available in the approximation
  5602. * callback from `this.dataGroupInfo`. It can be used to extract information
  5603. * from the raw data.
  5604. *
  5605. * Defaults to `average` for line-type series, `sum` for columns, `range`
  5606. * for range series and `ohlc` for OHLC and candlestick.
  5607. *
  5608. * @sample {highstock} stock/plotoptions/series-datagrouping-approximation
  5609. * Approximation callback with custom data
  5610. *
  5611. * @type {Highcharts.DataGroupingApproximationValue|Function}
  5612. * @apioption plotOptions.series.dataGrouping.approximation
  5613. */
  5614. /**
  5615. * Datetime formats for the header of the tooltip in a stock chart.
  5616. * The format can vary within a chart depending on the currently selected
  5617. * time range and the current data grouping.
  5618. *
  5619. * The default formats are:
  5620. * ```js
  5621. * {
  5622. * millisecond: [
  5623. * '%A, %b %e, %H:%M:%S.%L', '%A, %b %e, %H:%M:%S.%L', '-%H:%M:%S.%L'
  5624. * ],
  5625. * second: ['%A, %b %e, %H:%M:%S', '%A, %b %e, %H:%M:%S', '-%H:%M:%S'],
  5626. * minute: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
  5627. * hour: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
  5628. * day: ['%A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
  5629. * week: ['Week from %A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
  5630. * month: ['%B %Y', '%B', '-%B %Y'],
  5631. * year: ['%Y', '%Y', '-%Y']
  5632. * }
  5633. * ```
  5634. *
  5635. * For each of these array definitions, the first item is the format
  5636. * used when the active time span is one unit. For instance, if the
  5637. * current data applies to one week, the first item of the week array
  5638. * is used. The second and third items are used when the active time
  5639. * span is more than two units. For instance, if the current data applies
  5640. * to two weeks, the second and third item of the week array are used,
  5641. * and applied to the start and end date of the time span.
  5642. *
  5643. * @type {object}
  5644. * @apioption plotOptions.series.dataGrouping.dateTimeLabelFormats
  5645. */
  5646. /**
  5647. * Enable or disable data grouping.
  5648. *
  5649. * @type {boolean}
  5650. * @default true
  5651. * @apioption plotOptions.series.dataGrouping.enabled
  5652. */
  5653. /**
  5654. * When data grouping is forced, it runs no matter how small the intervals
  5655. * are. This can be handy for example when the sum should be calculated
  5656. * for values appearing at random times within each hour.
  5657. *
  5658. * @type {boolean}
  5659. * @default false
  5660. * @apioption plotOptions.series.dataGrouping.forced
  5661. */
  5662. /**
  5663. * The approximate pixel width of each group. If for example a series
  5664. * with 30 points is displayed over a 600 pixel wide plot area, no grouping
  5665. * is performed. If however the series contains so many points that
  5666. * the spacing is less than the groupPixelWidth, Highcharts will try
  5667. * to group it into appropriate groups so that each is more or less
  5668. * two pixels wide. If multiple series with different group pixel widths
  5669. * are drawn on the same x axis, all series will take the greatest width.
  5670. * For example, line series have 2px default group width, while column
  5671. * series have 10px. If combined, both the line and the column will
  5672. * have 10px by default.
  5673. *
  5674. * @type {number}
  5675. * @default 2
  5676. * @apioption plotOptions.series.dataGrouping.groupPixelWidth
  5677. */
  5678. /**
  5679. * By default only points within the visible range are grouped. Enabling this
  5680. * option will force data grouping to calculate all grouped points for a given
  5681. * dataset. That option prevents for example a column series from calculating
  5682. * a grouped point partially. The effect is similar to
  5683. * [Series.getExtremesFromAll](#plotOptions.series.getExtremesFromAll) but does
  5684. * not affect yAxis extremes.
  5685. *
  5686. * @sample {highstock} stock/plotoptions/series-datagrouping-groupall/
  5687. * Two series with the same data but different groupAll setting
  5688. *
  5689. * @type {boolean}
  5690. * @default false
  5691. * @since 6.1.0
  5692. * @apioption plotOptions.series.dataGrouping.groupAll
  5693. */
  5694. /**
  5695. * Normally, a group is indexed by the start of that group, so for example
  5696. * when 30 daily values are grouped into one month, that month's x value
  5697. * will be the 1st of the month. This apparently shifts the data to
  5698. * the left. When the smoothed option is true, this is compensated for.
  5699. * The data is shifted to the middle of the group, and min and max
  5700. * values are preserved. Internally, this is used in the Navigator series.
  5701. *
  5702. * @type {boolean}
  5703. * @default false
  5704. * @apioption plotOptions.series.dataGrouping.smoothed
  5705. */
  5706. /**
  5707. * An array determining what time intervals the data is allowed to be
  5708. * grouped to. Each array item is an array where the first value is
  5709. * the time unit and the second value another array of allowed multiples.
  5710. *
  5711. * Defaults to:
  5712. * ```js
  5713. * units: [[
  5714. * 'millisecond', // unit name
  5715. * [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
  5716. * ], [
  5717. * 'second',
  5718. * [1, 2, 5, 10, 15, 30]
  5719. * ], [
  5720. * 'minute',
  5721. * [1, 2, 5, 10, 15, 30]
  5722. * ], [
  5723. * 'hour',
  5724. * [1, 2, 3, 4, 6, 8, 12]
  5725. * ], [
  5726. * 'day',
  5727. * [1]
  5728. * ], [
  5729. * 'week',
  5730. * [1]
  5731. * ], [
  5732. * 'month',
  5733. * [1, 3, 6]
  5734. * ], [
  5735. * 'year',
  5736. * null
  5737. * ]]
  5738. * ```
  5739. *
  5740. * @type {Array<Array<string,(Array<number>|null)>>}
  5741. * @apioption plotOptions.series.dataGrouping.units
  5742. */
  5743. /**
  5744. * The approximate pixel width of each group. If for example a series
  5745. * with 30 points is displayed over a 600 pixel wide plot area, no grouping
  5746. * is performed. If however the series contains so many points that
  5747. * the spacing is less than the groupPixelWidth, Highcharts will try
  5748. * to group it into appropriate groups so that each is more or less
  5749. * two pixels wide. Defaults to `10`.
  5750. *
  5751. * @sample {highstock} stock/plotoptions/series-datagrouping-grouppixelwidth/
  5752. * Two series with the same data density but different groupPixelWidth
  5753. *
  5754. * @type {number}
  5755. * @default 10
  5756. * @apioption plotOptions.column.dataGrouping.groupPixelWidth
  5757. */
  5758. ''; // required by JSDoc parsing
  5759. return dataGrouping;
  5760. });
  5761. _registerModule(_modules, 'Series/OHLCSeries.js', [_modules['Core/Globals.js'], _modules['Core/Series/Point.js'], _modules['Core/Utilities.js']], function (H, Point, U) {
  5762. /* *
  5763. *
  5764. * (c) 2010-2020 Torstein Honsi
  5765. *
  5766. * License: www.highcharts.com/license
  5767. *
  5768. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  5769. *
  5770. * */
  5771. var seriesType = U.seriesType;
  5772. var seriesTypes = H.seriesTypes;
  5773. /**
  5774. * The ohlc series type.
  5775. *
  5776. * @private
  5777. * @class
  5778. * @name Highcharts.seriesTypes.ohlc
  5779. *
  5780. * @augments Highcharts.Series
  5781. */
  5782. seriesType('ohlc', 'column'
  5783. /**
  5784. * An OHLC chart is a style of financial chart used to describe price
  5785. * movements over time. It displays open, high, low and close values per
  5786. * data point.
  5787. *
  5788. * @sample stock/demo/ohlc/
  5789. * OHLC chart
  5790. *
  5791. * @extends plotOptions.column
  5792. * @excluding borderColor, borderRadius, borderWidth, crisp, stacking,
  5793. * stack
  5794. * @product highstock
  5795. * @optionparent plotOptions.ohlc
  5796. */
  5797. , {
  5798. /**
  5799. * The approximate pixel width of each group. If for example a series
  5800. * with 30 points is displayed over a 600 pixel wide plot area, no
  5801. * grouping is performed. If however the series contains so many points
  5802. * that the spacing is less than the groupPixelWidth, Highcharts will
  5803. * try to group it into appropriate groups so that each is more or less
  5804. * two pixels wide. Defaults to `5`.
  5805. *
  5806. * @type {number}
  5807. * @default 5
  5808. * @product highstock
  5809. * @apioption plotOptions.ohlc.dataGrouping.groupPixelWidth
  5810. */
  5811. /**
  5812. * The pixel width of the line/border. Defaults to `1`.
  5813. *
  5814. * @sample {highstock} stock/plotoptions/ohlc-linewidth/
  5815. * A greater line width
  5816. *
  5817. * @type {number}
  5818. * @default 1
  5819. * @product highstock
  5820. *
  5821. * @private
  5822. */
  5823. lineWidth: 1,
  5824. tooltip: {
  5825. pointFormat: '<span style="color:{point.color}">\u25CF</span> ' +
  5826. '<b> {series.name}</b><br/>' +
  5827. 'Open: {point.open}<br/>' +
  5828. 'High: {point.high}<br/>' +
  5829. 'Low: {point.low}<br/>' +
  5830. 'Close: {point.close}<br/>'
  5831. },
  5832. threshold: null,
  5833. states: {
  5834. /**
  5835. * @extends plotOptions.column.states.hover
  5836. * @product highstock
  5837. */
  5838. hover: {
  5839. /**
  5840. * The pixel width of the line representing the OHLC point.
  5841. *
  5842. * @type {number}
  5843. * @default 3
  5844. * @product highstock
  5845. */
  5846. lineWidth: 3
  5847. }
  5848. },
  5849. /**
  5850. * Determines which one of `open`, `high`, `low`, `close` values should
  5851. * be represented as `point.y`, which is later used to set dataLabel
  5852. * position and [compare](#plotOptions.series.compare).
  5853. *
  5854. * @sample {highstock} stock/plotoptions/ohlc-pointvalkey/
  5855. * Possible values
  5856. *
  5857. * @type {string}
  5858. * @default close
  5859. * @validvalue ["open", "high", "low", "close"]
  5860. * @product highstock
  5861. * @apioption plotOptions.ohlc.pointValKey
  5862. */
  5863. /**
  5864. * @default close
  5865. * @apioption plotOptions.ohlc.colorKey
  5866. */
  5867. /**
  5868. * Line color for up points.
  5869. *
  5870. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  5871. * @product highstock
  5872. * @apioption plotOptions.ohlc.upColor
  5873. */
  5874. stickyTracking: true
  5875. },
  5876. /**
  5877. * @lends Highcharts.seriesTypes.ohlc
  5878. */
  5879. {
  5880. /* eslint-disable valid-jsdoc */
  5881. directTouch: false,
  5882. pointArrayMap: ['open', 'high', 'low', 'close'],
  5883. toYData: function (point) {
  5884. // return a plain array for speedy calculation
  5885. return [point.open, point.high, point.low, point.close];
  5886. },
  5887. pointValKey: 'close',
  5888. pointAttrToOptions: {
  5889. stroke: 'color',
  5890. 'stroke-width': 'lineWidth'
  5891. },
  5892. /**
  5893. * @private
  5894. * @function Highcarts.seriesTypes.ohlc#init
  5895. * @return {void}
  5896. */
  5897. init: function () {
  5898. seriesTypes.column.prototype.init.apply(this, arguments);
  5899. this.options.stacking = void 0; // #8817
  5900. },
  5901. /**
  5902. * Postprocess mapping between options and SVG attributes
  5903. *
  5904. * @private
  5905. * @function Highcharts.seriesTypes.ohlc#pointAttribs
  5906. * @param {Highcharts.OHLCPoint} point
  5907. * @param {string} state
  5908. * @return {Highcharts.SVGAttributes}
  5909. */
  5910. pointAttribs: function (point, state) {
  5911. var attribs = seriesTypes.column.prototype.pointAttribs.call(this,
  5912. point,
  5913. state),
  5914. options = this.options;
  5915. delete attribs.fill;
  5916. if (!point.options.color &&
  5917. options.upColor &&
  5918. point.open < point.close) {
  5919. attribs.stroke = options.upColor;
  5920. }
  5921. return attribs;
  5922. },
  5923. /**
  5924. * Translate data points from raw values x and y to plotX and plotY
  5925. *
  5926. * @private
  5927. * @function Highcharts.seriesTypes.ohlc#translate
  5928. * @return {void}
  5929. */
  5930. translate: function () {
  5931. var series = this,
  5932. yAxis = series.yAxis,
  5933. hasModifyValue = !!series.modifyValue,
  5934. translated = [
  5935. 'plotOpen',
  5936. 'plotHigh',
  5937. 'plotLow',
  5938. 'plotClose',
  5939. 'yBottom'
  5940. ]; // translate OHLC for
  5941. seriesTypes.column.prototype.translate.apply(series);
  5942. // Do the translation
  5943. series.points.forEach(function (point) {
  5944. [point.open, point.high, point.low, point.close, point.low]
  5945. .forEach(function (value, i) {
  5946. if (value !== null) {
  5947. if (hasModifyValue) {
  5948. value = series.modifyValue(value);
  5949. }
  5950. point[translated[i]] =
  5951. yAxis.toPixels(value, true);
  5952. }
  5953. });
  5954. // Align the tooltip to the high value to avoid covering the
  5955. // point
  5956. point.tooltipPos[1] =
  5957. point.plotHigh + yAxis.pos - series.chart.plotTop;
  5958. });
  5959. },
  5960. /**
  5961. * Draw the data points
  5962. *
  5963. * @private
  5964. * @function Highcharts.seriesTypes.ohlc#drawPoints
  5965. * @return {void}
  5966. */
  5967. drawPoints: function () {
  5968. var series = this,
  5969. points = series.points,
  5970. chart = series.chart,
  5971. /**
  5972. * Extend vertical stem to open and close values.
  5973. */
  5974. extendStem = function (path,
  5975. halfStrokeWidth,
  5976. openOrClose) {
  5977. var start = path[0];
  5978. var end = path[1];
  5979. // We don't need to worry about crisp - openOrClose value
  5980. // is already crisped and halfStrokeWidth should remove it.
  5981. if (typeof start[2] === 'number') {
  5982. start[2] = Math.max(openOrClose + halfStrokeWidth, start[2]);
  5983. }
  5984. if (typeof end[2] === 'number') {
  5985. end[2] = Math.min(openOrClose - halfStrokeWidth, end[2]);
  5986. }
  5987. };
  5988. points.forEach(function (point) {
  5989. var plotOpen,
  5990. plotClose,
  5991. crispCorr,
  5992. halfWidth,
  5993. path,
  5994. graphic = point.graphic,
  5995. crispX,
  5996. isNew = !graphic,
  5997. strokeWidth;
  5998. if (typeof point.plotY !== 'undefined') {
  5999. // Create and/or update the graphic
  6000. if (!graphic) {
  6001. point.graphic = graphic = chart.renderer.path()
  6002. .add(series.group);
  6003. }
  6004. if (!chart.styledMode) {
  6005. graphic.attr(series.pointAttribs(point, (point.selected && 'select'))); // #3897
  6006. }
  6007. // crisp vector coordinates
  6008. strokeWidth = graphic.strokeWidth();
  6009. crispCorr = (strokeWidth % 2) / 2;
  6010. // #2596:
  6011. crispX = Math.round(point.plotX) - crispCorr;
  6012. halfWidth = Math.round(point.shapeArgs.width / 2);
  6013. // the vertical stem
  6014. path = [
  6015. ['M', crispX, Math.round(point.yBottom)],
  6016. ['L', crispX, Math.round(point.plotHigh)]
  6017. ];
  6018. // open
  6019. if (point.open !== null) {
  6020. plotOpen = Math.round(point.plotOpen) + crispCorr;
  6021. path.push(['M', crispX, plotOpen], ['L', crispX - halfWidth, plotOpen]);
  6022. extendStem(path, strokeWidth / 2, plotOpen);
  6023. }
  6024. // close
  6025. if (point.close !== null) {
  6026. plotClose = Math.round(point.plotClose) + crispCorr;
  6027. path.push(['M', crispX, plotClose], ['L', crispX + halfWidth, plotClose]);
  6028. extendStem(path, strokeWidth / 2, plotClose);
  6029. }
  6030. graphic[isNew ? 'attr' : 'animate']({ d: path })
  6031. .addClass(point.getClassName(), true);
  6032. }
  6033. });
  6034. },
  6035. animate: null // Disable animation
  6036. /* eslint-enable valid-jsdoc */
  6037. },
  6038. /**
  6039. * @lends Highcharts.seriesTypes.ohlc.prototype.pointClass.prototype
  6040. */
  6041. {
  6042. /* eslint-disable valid-jsdoc */
  6043. /**
  6044. * Extend the parent method by adding up or down to the class name.
  6045. * @private
  6046. * @function Highcharts.seriesTypes.ohlc#getClassName
  6047. * @return {string}
  6048. */
  6049. getClassName: function () {
  6050. return Point.prototype.getClassName.call(this) +
  6051. (this.open < this.close ?
  6052. ' highcharts-point-up' :
  6053. ' highcharts-point-down');
  6054. }
  6055. /* eslint-enable valid-jsdoc */
  6056. });
  6057. /**
  6058. * A `ohlc` series. If the [type](#series.ohlc.type) option is not
  6059. * specified, it is inherited from [chart.type](#chart.type).
  6060. *
  6061. * @extends series,plotOptions.ohlc
  6062. * @excluding dataParser, dataURL
  6063. * @product highstock
  6064. * @apioption series.ohlc
  6065. */
  6066. /**
  6067. * An array of data points for the series. For the `ohlc` series type,
  6068. * points can be given in the following ways:
  6069. *
  6070. * 1. An array of arrays with 5 or 4 values. In this case, the values correspond
  6071. * to `x,open,high,low,close`. If the first value is a string, it is applied
  6072. * as the name of the point, and the `x` value is inferred. The `x` value can
  6073. * also be omitted, in which case the inner arrays should be of length 4\.
  6074. * Then the `x` value is automatically calculated, either starting at 0 and
  6075. * incremented by 1, or from `pointStart` and `pointInterval` given in the
  6076. * series options.
  6077. * ```js
  6078. * data: [
  6079. * [0, 6, 5, 6, 7],
  6080. * [1, 9, 4, 8, 2],
  6081. * [2, 6, 3, 4, 10]
  6082. * ]
  6083. * ```
  6084. *
  6085. * 2. An array of objects with named values. The following snippet shows only a
  6086. * few settings, see the complete options set below. If the total number of
  6087. * data points exceeds the series'
  6088. * [turboThreshold](#series.ohlc.turboThreshold), this option is not
  6089. * available.
  6090. * ```js
  6091. * data: [{
  6092. * x: 1,
  6093. * open: 3,
  6094. * high: 4,
  6095. * low: 5,
  6096. * close: 2,
  6097. * name: "Point2",
  6098. * color: "#00FF00"
  6099. * }, {
  6100. * x: 1,
  6101. * open: 4,
  6102. * high: 3,
  6103. * low: 6,
  6104. * close: 7,
  6105. * name: "Point1",
  6106. * color: "#FF00FF"
  6107. * }]
  6108. * ```
  6109. *
  6110. * @type {Array<Array<(number|string),number,number,number>|Array<(number|string),number,number,number,number>|*>}
  6111. * @extends series.arearange.data
  6112. * @excluding y, marker
  6113. * @product highstock
  6114. * @apioption series.ohlc.data
  6115. */
  6116. /**
  6117. * The closing value of each data point.
  6118. *
  6119. * @type {number}
  6120. * @product highstock
  6121. * @apioption series.ohlc.data.close
  6122. */
  6123. /**
  6124. * The opening value of each data point.
  6125. *
  6126. * @type {number}
  6127. * @product highstock
  6128. * @apioption series.ohlc.data.open
  6129. */
  6130. ''; // adds doclets above to transpilat
  6131. });
  6132. _registerModule(_modules, 'Series/CandlestickSeries.js', [_modules['Core/Globals.js'], _modules['Core/Options.js'], _modules['Core/Utilities.js']], function (H, O, U) {
  6133. /* *
  6134. *
  6135. * (c) 2010-2020 Torstein Honsi
  6136. *
  6137. * License: www.highcharts.com/license
  6138. *
  6139. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  6140. *
  6141. * */
  6142. var defaultOptions = O.defaultOptions;
  6143. var merge = U.merge,
  6144. seriesType = U.seriesType;
  6145. var seriesTypes = H.seriesTypes;
  6146. /**
  6147. * A candlestick chart is a style of financial chart used to describe price
  6148. * movements over time.
  6149. *
  6150. * @sample stock/demo/candlestick/
  6151. * Candlestick chart
  6152. *
  6153. * @extends plotOptions.ohlc
  6154. * @excluding borderColor,borderRadius,borderWidth
  6155. * @product highstock
  6156. * @optionparent plotOptions.candlestick
  6157. */
  6158. var candlestickOptions = {
  6159. /**
  6160. * The specific line color for up candle sticks. The default is to inherit
  6161. * the general `lineColor` setting.
  6162. *
  6163. * @sample {highstock} stock/plotoptions/candlestick-linecolor/
  6164. * Candlestick line colors
  6165. *
  6166. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6167. * @since 1.3.6
  6168. * @product highstock
  6169. * @apioption plotOptions.candlestick.upLineColor
  6170. */
  6171. /**
  6172. * @type {Highcharts.DataGroupingApproximationValue|Function}
  6173. * @default ohlc
  6174. * @product highstock
  6175. * @apioption plotOptions.candlestick.dataGrouping.approximation
  6176. */
  6177. states: {
  6178. /**
  6179. * @extends plotOptions.column.states.hover
  6180. * @product highstock
  6181. */
  6182. hover: {
  6183. /**
  6184. * The pixel width of the line/border around the candlestick.
  6185. *
  6186. * @product highstock
  6187. */
  6188. lineWidth: 2
  6189. }
  6190. },
  6191. /**
  6192. * @extends plotOptions.ohlc.tooltip
  6193. */
  6194. tooltip: defaultOptions.plotOptions.ohlc.tooltip,
  6195. /**
  6196. * @type {number|null}
  6197. * @product highstock
  6198. */
  6199. threshold: null,
  6200. /**
  6201. * The color of the line/border of the candlestick.
  6202. *
  6203. * In styled mode,
  6204. the line stroke can be set with the
  6205. * `.highcharts-candlestick-series .highcahrts-point` rule.
  6206. *
  6207. * @see [upLineColor](#plotOptions.candlestick.upLineColor)
  6208. *
  6209. * @sample {highstock} stock/plotoptions/candlestick-linecolor/
  6210. * Candlestick line colors
  6211. *
  6212. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6213. * @default #000000
  6214. * @product highstock
  6215. */
  6216. lineColor: '#000000',
  6217. /**
  6218. * The pixel width of the candlestick line/border. Defaults to `1`.
  6219. *
  6220. *
  6221. * In styled mode,
  6222. the line stroke width can be set with the
  6223. * `.highcharts-candlestick-series .highcahrts-point` rule.
  6224. *
  6225. * @product highstock
  6226. */
  6227. lineWidth: 1,
  6228. /**
  6229. * The fill color of the candlestick when values are rising.
  6230. *
  6231. * In styled mode,
  6232. the up color can be set with the
  6233. * `.highcharts-candlestick-series .highcharts-point-up` rule.
  6234. *
  6235. * @sample {highstock} stock/plotoptions/candlestick-color/
  6236. * Custom colors
  6237. * @sample {highstock} highcharts/css/candlestick/
  6238. * Colors in styled mode
  6239. *
  6240. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6241. * @default #ffffff
  6242. * @product highstock
  6243. */
  6244. upColor: '#ffffff',
  6245. /**
  6246. * @product highstock
  6247. */
  6248. stickyTracking: true
  6249. };
  6250. /**
  6251. * The candlestick series type.
  6252. *
  6253. * @private
  6254. * @class
  6255. * @name Highcharts.seriesTypes.candlestick
  6256. *
  6257. * @augments Highcharts.seriesTypes.ohlc
  6258. */
  6259. seriesType('candlestick', 'ohlc', merge(defaultOptions.plotOptions.column, candlestickOptions),
  6260. /**
  6261. * @lends seriesTypes.candlestick
  6262. */
  6263. {
  6264. /* eslint-disable valid-jsdoc */
  6265. /**
  6266. * Postprocess mapping between options and SVG attributes
  6267. *
  6268. * @private
  6269. * @function Highcharts.seriesTypes.candlestick#pointAttribs
  6270. * @param {Highcharts.Point} point
  6271. * @param {string} [state]
  6272. * @return {Highcharts.SVGAttributes}
  6273. */
  6274. pointAttribs: function (point, state) {
  6275. var attribs = seriesTypes.column.prototype.pointAttribs.call(this,
  6276. point,
  6277. state),
  6278. options = this.options,
  6279. isUp = point.open < point.close,
  6280. stroke = options.lineColor || this.color,
  6281. stateOptions;
  6282. attribs['stroke-width'] = options.lineWidth;
  6283. attribs.fill = point.options.color ||
  6284. (isUp ? (options.upColor || this.color) : this.color);
  6285. attribs.stroke = point.options.lineColor ||
  6286. (isUp ? (options.upLineColor || stroke) : stroke);
  6287. // Select or hover states
  6288. if (state) {
  6289. stateOptions = options.states[state];
  6290. attribs.fill = stateOptions.color || attribs.fill;
  6291. attribs.stroke = stateOptions.lineColor || attribs.stroke;
  6292. attribs['stroke-width'] =
  6293. stateOptions.lineWidth || attribs['stroke-width'];
  6294. }
  6295. return attribs;
  6296. },
  6297. /**
  6298. * Draw the data points.
  6299. *
  6300. * @private
  6301. * @function Highcharts.seriesTypes.candlestick#drawPoints
  6302. * @return {void}
  6303. */
  6304. drawPoints: function () {
  6305. var series = this,
  6306. points = series.points,
  6307. chart = series.chart,
  6308. reversedYAxis = series.yAxis.reversed;
  6309. points.forEach(function (point) {
  6310. var graphic = point.graphic,
  6311. plotOpen,
  6312. plotClose,
  6313. topBox,
  6314. bottomBox,
  6315. hasTopWhisker,
  6316. hasBottomWhisker,
  6317. crispCorr,
  6318. crispX,
  6319. path,
  6320. halfWidth,
  6321. isNew = !graphic;
  6322. if (typeof point.plotY !== 'undefined') {
  6323. if (!graphic) {
  6324. point.graphic = graphic = chart.renderer.path()
  6325. .add(series.group);
  6326. }
  6327. if (!series.chart.styledMode) {
  6328. graphic
  6329. .attr(series.pointAttribs(point, (point.selected && 'select'))) // #3897
  6330. .shadow(series.options.shadow);
  6331. }
  6332. // Crisp vector coordinates
  6333. crispCorr = (graphic.strokeWidth() % 2) / 2;
  6334. // #2596:
  6335. crispX = Math.round(point.plotX) - crispCorr;
  6336. plotOpen = point.plotOpen;
  6337. plotClose = point.plotClose;
  6338. topBox = Math.min(plotOpen, plotClose);
  6339. bottomBox = Math.max(plotOpen, plotClose);
  6340. halfWidth = Math.round(point.shapeArgs.width / 2);
  6341. hasTopWhisker = reversedYAxis ?
  6342. bottomBox !== point.yBottom :
  6343. Math.round(topBox) !==
  6344. Math.round(point.plotHigh);
  6345. hasBottomWhisker = reversedYAxis ?
  6346. Math.round(topBox) !==
  6347. Math.round(point.plotHigh) :
  6348. bottomBox !== point.yBottom;
  6349. topBox = Math.round(topBox) + crispCorr;
  6350. bottomBox = Math.round(bottomBox) + crispCorr;
  6351. // Create the path. Due to a bug in Chrome 49, the path is
  6352. // first instanciated with no values, then the values
  6353. // pushed. For unknown reasons, instanciating the path array
  6354. // with all the values would lead to a crash when updating
  6355. // frequently (#5193).
  6356. path = [];
  6357. path.push(['M', crispX - halfWidth, bottomBox], ['L', crispX - halfWidth, topBox], ['L', crispX + halfWidth, topBox], ['L', crispX + halfWidth, bottomBox], ['Z'], // Ensure a nice rectangle #2602
  6358. ['M', crispX, topBox], [
  6359. 'L',
  6360. // #460, #2094
  6361. crispX,
  6362. hasTopWhisker ?
  6363. Math.round(reversedYAxis ?
  6364. point.yBottom :
  6365. point.plotHigh) :
  6366. topBox
  6367. ], ['M', crispX, bottomBox], [
  6368. 'L',
  6369. // #460, #2094
  6370. crispX,
  6371. hasBottomWhisker ?
  6372. Math.round(reversedYAxis ?
  6373. point.plotHigh :
  6374. point.yBottom) :
  6375. bottomBox
  6376. ]);
  6377. graphic[isNew ? 'attr' : 'animate']({ d: path })
  6378. .addClass(point.getClassName(), true);
  6379. }
  6380. });
  6381. /* eslint-enable valid-jsdoc */
  6382. }
  6383. });
  6384. /**
  6385. * A `candlestick` series. If the [type](#series.candlestick.type)
  6386. * option is not specified, it is inherited from [chart.type](
  6387. * #chart.type).
  6388. *
  6389. * @type {*}
  6390. * @extends series,plotOptions.candlestick
  6391. * @excluding dataParser, dataURL
  6392. * @product highstock
  6393. * @apioption series.candlestick
  6394. */
  6395. /**
  6396. * An array of data points for the series. For the `candlestick` series
  6397. * type, points can be given in the following ways:
  6398. *
  6399. * 1. An array of arrays with 5 or 4 values. In this case, the values correspond
  6400. * to `x,open,high,low,close`. If the first value is a string, it is applied
  6401. * as the name of the point, and the `x` value is inferred. The `x` value can
  6402. * also be omitted, in which case the inner arrays should be of length 4.
  6403. * Then the `x` value is automatically calculated, either starting at 0 and
  6404. * incremented by 1, or from `pointStart` and `pointInterval` given in the
  6405. * series options.
  6406. * ```js
  6407. * data: [
  6408. * [0, 7, 2, 0, 4],
  6409. * [1, 1, 4, 2, 8],
  6410. * [2, 3, 3, 9, 3]
  6411. * ]
  6412. * ```
  6413. *
  6414. * 2. An array of objects with named values. The following snippet shows only a
  6415. * few settings, see the complete options set below. If the total number of
  6416. * data points exceeds the series'
  6417. * [turboThreshold](#series.candlestick.turboThreshold), this option is not
  6418. * available.
  6419. * ```js
  6420. * data: [{
  6421. * x: 1,
  6422. * open: 9,
  6423. * high: 2,
  6424. * low: 4,
  6425. * close: 6,
  6426. * name: "Point2",
  6427. * color: "#00FF00"
  6428. * }, {
  6429. * x: 1,
  6430. * open: 1,
  6431. * high: 4,
  6432. * low: 7,
  6433. * close: 7,
  6434. * name: "Point1",
  6435. * color: "#FF00FF"
  6436. * }]
  6437. * ```
  6438. *
  6439. * @type {Array<Array<(number|string),number,number,number>|Array<(number|string),number,number,number,number>|*>}
  6440. * @extends series.ohlc.data
  6441. * @excluding y
  6442. * @product highstock
  6443. * @apioption series.candlestick.data
  6444. */
  6445. ''; // adds doclets above to transpilat
  6446. });
  6447. _registerModule(_modules, 'Mixins/OnSeries.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) {
  6448. /* *
  6449. *
  6450. * (c) 2010-2020 Torstein Honsi
  6451. *
  6452. * License: www.highcharts.com/license
  6453. *
  6454. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  6455. *
  6456. * */
  6457. var defined = U.defined,
  6458. stableSort = U.stableSort;
  6459. var seriesTypes = H.seriesTypes;
  6460. /**
  6461. * @private
  6462. * @mixin onSeriesMixin
  6463. */
  6464. var onSeriesMixin = {
  6465. /* eslint-disable valid-jsdoc */
  6466. /**
  6467. * Override getPlotBox. If the onSeries option is valid,
  6468. return the plot box
  6469. * of the onSeries,
  6470. otherwise proceed as usual.
  6471. *
  6472. * @private
  6473. * @function onSeriesMixin.getPlotBox
  6474. * @return {Highcharts.SeriesPlotBoxObject}
  6475. */
  6476. getPlotBox: function () {
  6477. return H.Series.prototype.getPlotBox.call((this.options.onSeries &&
  6478. this.chart.get(this.options.onSeries)) || this);
  6479. },
  6480. /**
  6481. * Extend the translate method by placing the point on the related series
  6482. *
  6483. * @private
  6484. * @function onSeriesMixin.translate
  6485. * @return {void}
  6486. */
  6487. translate: function () {
  6488. seriesTypes.column.prototype.translate.apply(this);
  6489. var series = this,
  6490. options = series.options,
  6491. chart = series.chart,
  6492. points = series.points,
  6493. cursor = points.length - 1,
  6494. point,
  6495. lastPoint,
  6496. optionsOnSeries = options.onSeries,
  6497. onSeries = (optionsOnSeries &&
  6498. chart.get(optionsOnSeries)),
  6499. onKey = options.onKey || 'y',
  6500. step = onSeries && onSeries.options.step,
  6501. onData = (onSeries && onSeries.points),
  6502. i = onData && onData.length,
  6503. inverted = chart.inverted,
  6504. xAxis = series.xAxis,
  6505. yAxis = series.yAxis,
  6506. xOffset = 0,
  6507. leftPoint,
  6508. lastX,
  6509. rightPoint,
  6510. currentDataGrouping,
  6511. distanceRatio;
  6512. // relate to a master series
  6513. if (onSeries && onSeries.visible && i) {
  6514. xOffset = (onSeries.pointXOffset || 0) + (onSeries.barW || 0) / 2;
  6515. currentDataGrouping = onSeries.currentDataGrouping;
  6516. lastX = (onData[i - 1].x +
  6517. (currentDataGrouping ? currentDataGrouping.totalRange : 0)); // #2374
  6518. // sort the data points
  6519. stableSort(points, function (a, b) {
  6520. return (a.x - b.x);
  6521. });
  6522. onKey = 'plot' + onKey[0].toUpperCase() + onKey.substr(1);
  6523. while (i-- && points[cursor]) {
  6524. leftPoint = onData[i];
  6525. point = points[cursor];
  6526. point.y = leftPoint.y;
  6527. if (leftPoint.x <= point.x &&
  6528. typeof leftPoint[onKey] !== 'undefined') {
  6529. if (point.x <= lastX) { // #803
  6530. point.plotY = leftPoint[onKey];
  6531. // interpolate between points, #666
  6532. if (leftPoint.x < point.x &&
  6533. !step) {
  6534. rightPoint = onData[i + 1];
  6535. if (rightPoint &&
  6536. typeof rightPoint[onKey] !== 'undefined') {
  6537. // the distance ratio, between 0 and 1
  6538. distanceRatio =
  6539. (point.x - leftPoint.x) /
  6540. (rightPoint.x - leftPoint.x);
  6541. point.plotY +=
  6542. distanceRatio *
  6543. // the plotY distance
  6544. (rightPoint[onKey] - leftPoint[onKey]);
  6545. point.y +=
  6546. distanceRatio *
  6547. (rightPoint.y - leftPoint.y);
  6548. }
  6549. }
  6550. }
  6551. cursor--;
  6552. i++; // check again for points in the same x position
  6553. if (cursor < 0) {
  6554. break;
  6555. }
  6556. }
  6557. }
  6558. }
  6559. // Add plotY position and handle stacking
  6560. points.forEach(function (point, i) {
  6561. var stackIndex;
  6562. point.plotX += xOffset; // #2049
  6563. // Undefined plotY means the point is either on axis, outside series
  6564. // range or hidden series. If the series is outside the range of the
  6565. // x axis it should fall through with an undefined plotY, but then
  6566. // we must remove the shapeArgs (#847). For inverted charts, we need
  6567. // to calculate position anyway, because series.invertGroups is not
  6568. // defined
  6569. if (typeof point.plotY === 'undefined' || inverted) {
  6570. if (point.plotX >= 0 &&
  6571. point.plotX <= xAxis.len) {
  6572. // We're inside xAxis range
  6573. if (inverted) {
  6574. point.plotY = xAxis.translate(point.x, 0, 1, 0, 1);
  6575. point.plotX = defined(point.y) ?
  6576. yAxis.translate(point.y, 0, 0, 0, 1) :
  6577. 0;
  6578. }
  6579. else {
  6580. point.plotY = (xAxis.opposite ? 0 : series.yAxis.len) +
  6581. xAxis.offset; // For the windbarb demo
  6582. }
  6583. }
  6584. else {
  6585. point.shapeArgs = {}; // 847
  6586. }
  6587. }
  6588. // if multiple flags appear at the same x, order them into a stack
  6589. lastPoint = points[i - 1];
  6590. if (lastPoint && lastPoint.plotX === point.plotX) {
  6591. if (typeof lastPoint.stackIndex === 'undefined') {
  6592. lastPoint.stackIndex = 0;
  6593. }
  6594. stackIndex = lastPoint.stackIndex + 1;
  6595. }
  6596. point.stackIndex = stackIndex; // #3639
  6597. });
  6598. this.onSeries = onSeries;
  6599. }
  6600. /* eslint-enable valid-jsdoc */
  6601. };
  6602. return onSeriesMixin;
  6603. });
  6604. _registerModule(_modules, 'Series/FlagsSeries.js', [_modules['Core/Globals.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Renderer/SVG/SVGRenderer.js'], _modules['Core/Utilities.js'], _modules['Mixins/OnSeries.js']], function (H, SVGElement, SVGRenderer, U, onSeriesMixin) {
  6605. /* *
  6606. *
  6607. * (c) 2010-2020 Torstein Honsi
  6608. *
  6609. * License: www.highcharts.com/license
  6610. *
  6611. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  6612. *
  6613. * */
  6614. var addEvent = U.addEvent,
  6615. defined = U.defined,
  6616. isNumber = U.isNumber,
  6617. merge = U.merge,
  6618. objectEach = U.objectEach,
  6619. seriesType = U.seriesType,
  6620. wrap = U.wrap;
  6621. /**
  6622. * @typedef {"circlepin"|"flag"|"squarepin"} Highcharts.FlagsShapeValue
  6623. */
  6624. var noop = H.noop,
  6625. Renderer = H.Renderer,
  6626. Series = H.Series,
  6627. TrackerMixin = H.TrackerMixin,
  6628. VMLRenderer = H.VMLRenderer,
  6629. symbols = SVGRenderer.prototype.symbols;
  6630. /**
  6631. * The Flags series.
  6632. *
  6633. * @private
  6634. * @class
  6635. * @name Highcharts.seriesTypes.flags
  6636. *
  6637. * @augments Highcharts.Series
  6638. */
  6639. seriesType('flags', 'column'
  6640. /**
  6641. * Flags are used to mark events in stock charts. They can be added on the
  6642. * timeline, or attached to a specific series.
  6643. *
  6644. * @sample stock/demo/flags-general/
  6645. * Flags on a line series
  6646. *
  6647. * @extends plotOptions.column
  6648. * @excluding animation, borderColor, borderRadius, borderWidth,
  6649. * colorByPoint, dataGrouping, pointPadding, pointWidth,
  6650. * turboThreshold
  6651. * @product highstock
  6652. * @optionparent plotOptions.flags
  6653. */
  6654. , {
  6655. /**
  6656. * In case the flag is placed on a series, on what point key to place
  6657. * it. Line and columns have one key, `y`. In range or OHLC-type series,
  6658. * however, the flag can optionally be placed on the `open`, `high`,
  6659. * `low` or `close` key.
  6660. *
  6661. * @sample {highstock} stock/plotoptions/flags-onkey/
  6662. * Range series, flag on high
  6663. *
  6664. * @type {string}
  6665. * @default y
  6666. * @since 4.2.2
  6667. * @product highstock
  6668. * @validvalue ["y", "open", "high", "low", "close"]
  6669. * @apioption plotOptions.flags.onKey
  6670. */
  6671. /**
  6672. * The id of the series that the flags should be drawn on. If no id
  6673. * is given, the flags are drawn on the x axis.
  6674. *
  6675. * @sample {highstock} stock/plotoptions/flags/
  6676. * Flags on series and on x axis
  6677. *
  6678. * @type {string}
  6679. * @product highstock
  6680. * @apioption plotOptions.flags.onSeries
  6681. */
  6682. pointRange: 0,
  6683. /**
  6684. * Whether the flags are allowed to overlap sideways. If `false`, the
  6685. * flags are moved sideways using an algorithm that seeks to place every
  6686. * flag as close as possible to its original position.
  6687. *
  6688. * @sample {highstock} stock/plotoptions/flags-allowoverlapx
  6689. * Allow sideways overlap
  6690. *
  6691. * @since 6.0.4
  6692. */
  6693. allowOverlapX: false,
  6694. /**
  6695. * The shape of the marker. Can be one of "flag", "circlepin",
  6696. * "squarepin", or an image of the format `url(/path-to-image.jpg)`.
  6697. * Individual shapes can also be set for each point.
  6698. *
  6699. * @sample {highstock} stock/plotoptions/flags/
  6700. * Different shapes
  6701. *
  6702. * @type {Highcharts.FlagsShapeValue}
  6703. * @product highstock
  6704. */
  6705. shape: 'flag',
  6706. /**
  6707. * When multiple flags in the same series fall on the same value, this
  6708. * number determines the vertical offset between them.
  6709. *
  6710. * @sample {highstock} stock/plotoptions/flags-stackdistance/
  6711. * A greater stack distance
  6712. *
  6713. * @product highstock
  6714. */
  6715. stackDistance: 12,
  6716. /**
  6717. * Text alignment for the text inside the flag.
  6718. *
  6719. * @since 5.0.0
  6720. * @product highstock
  6721. * @validvalue ["left", "center", "right"]
  6722. */
  6723. textAlign: 'center',
  6724. /**
  6725. * Specific tooltip options for flag series. Flag series tooltips are
  6726. * different from most other types in that a flag doesn't have a data
  6727. * value, so the tooltip rather displays the `text` option for each
  6728. * point.
  6729. *
  6730. * @extends plotOptions.series.tooltip
  6731. * @excluding changeDecimals, valueDecimals, valuePrefix, valueSuffix
  6732. * @product highstock
  6733. */
  6734. tooltip: {
  6735. pointFormat: '{point.text}<br/>'
  6736. },
  6737. threshold: null,
  6738. /**
  6739. * The text to display on each flag. This can be defined on series
  6740. * level, or individually for each point. Defaults to `"A"`.
  6741. *
  6742. * @type {string}
  6743. * @default A
  6744. * @product highstock
  6745. * @apioption plotOptions.flags.title
  6746. */
  6747. /**
  6748. * The y position of the top left corner of the flag relative to either
  6749. * the series (if onSeries is defined), or the x axis. Defaults to
  6750. * `-30`.
  6751. *
  6752. * @product highstock
  6753. */
  6754. y: -30,
  6755. /**
  6756. * Whether to use HTML to render the flag texts. Using HTML allows for
  6757. * advanced formatting, images and reliable bi-directional text
  6758. * rendering. Note that exported images won't respect the HTML, and that
  6759. * HTML won't respect Z-index settings.
  6760. *
  6761. * @type {boolean}
  6762. * @default false
  6763. * @since 1.3
  6764. * @product highstock
  6765. * @apioption plotOptions.flags.useHTML
  6766. */
  6767. /**
  6768. * Fixed width of the flag's shape. By default, width is autocalculated
  6769. * according to the flag's title.
  6770. *
  6771. * @sample {highstock} stock/demo/flags-shapes/
  6772. * Flags with fixed width
  6773. *
  6774. * @type {number}
  6775. * @product highstock
  6776. * @apioption plotOptions.flags.width
  6777. */
  6778. /**
  6779. * Fixed height of the flag's shape. By default, height is
  6780. * autocalculated according to the flag's title.
  6781. *
  6782. * @type {number}
  6783. * @product highstock
  6784. * @apioption plotOptions.flags.height
  6785. */
  6786. /**
  6787. * The fill color for the flags.
  6788. *
  6789. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6790. * @product highstock
  6791. */
  6792. fillColor: '#ffffff',
  6793. /**
  6794. * The color of the line/border of the flag.
  6795. *
  6796. * In styled mode, the stroke is set in the
  6797. * `.highcharts-flag-series.highcharts-point` rule.
  6798. *
  6799. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6800. * @default #000000
  6801. * @product highstock
  6802. * @apioption plotOptions.flags.lineColor
  6803. */
  6804. /**
  6805. * The pixel width of the flag's line/border.
  6806. *
  6807. * @product highstock
  6808. */
  6809. lineWidth: 1,
  6810. states: {
  6811. /**
  6812. * @extends plotOptions.column.states.hover
  6813. * @product highstock
  6814. */
  6815. hover: {
  6816. /**
  6817. * The color of the line/border of the flag.
  6818. *
  6819. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6820. * @product highstock
  6821. */
  6822. lineColor: '#000000',
  6823. /**
  6824. * The fill or background color of the flag.
  6825. *
  6826. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6827. * @product highstock
  6828. */
  6829. fillColor: '#ccd6eb'
  6830. }
  6831. },
  6832. /**
  6833. * The text styles of the flag.
  6834. *
  6835. * In styled mode, the styles are set in the
  6836. * `.highcharts-flag-series .highcharts-point` rule.
  6837. *
  6838. * @type {Highcharts.CSSObject}
  6839. * @default {"fontSize": "11px", "fontWeight": "bold"}
  6840. * @product highstock
  6841. */
  6842. style: {
  6843. /** @ignore-option */
  6844. fontSize: '11px',
  6845. /** @ignore-option */
  6846. fontWeight: 'bold'
  6847. }
  6848. },
  6849. /**
  6850. * @lends seriesTypes.flags.prototype
  6851. */
  6852. {
  6853. sorted: false,
  6854. noSharedTooltip: true,
  6855. allowDG: false,
  6856. takeOrdinalPosition: false,
  6857. trackerGroups: ['markerGroup'],
  6858. forceCrop: true,
  6859. /* eslint-disable no-invalid-this, valid-jsdoc */
  6860. /**
  6861. * Inherit the initialization from base Series.
  6862. *
  6863. * @private
  6864. * @borrows Highcharts.Series#init as Highcharts.seriesTypes.flags#init
  6865. */
  6866. init: Series.prototype.init,
  6867. /**
  6868. * Get presentational attributes
  6869. *
  6870. * @private
  6871. * @function Highcharts.seriesTypes.flags#pointAttribs
  6872. *
  6873. * @param {Highcharts.Point} point
  6874. *
  6875. * @param {string} [state]
  6876. *
  6877. * @return {Highcharts.SVGAttributes}
  6878. */
  6879. pointAttribs: function (point, state) {
  6880. var options = this.options,
  6881. color = (point && point.color) || this.color,
  6882. lineColor = options.lineColor,
  6883. lineWidth = (point && point.lineWidth),
  6884. fill = (point && point.fillColor) || options.fillColor;
  6885. if (state) {
  6886. fill = options.states[state].fillColor;
  6887. lineColor = options.states[state].lineColor;
  6888. lineWidth = options.states[state].lineWidth;
  6889. }
  6890. return {
  6891. fill: fill || color,
  6892. stroke: lineColor || color,
  6893. 'stroke-width': lineWidth || options.lineWidth || 0
  6894. };
  6895. },
  6896. translate: onSeriesMixin.translate,
  6897. getPlotBox: onSeriesMixin.getPlotBox,
  6898. /**
  6899. * Draw the markers.
  6900. *
  6901. * @private
  6902. * @function Highcharts.seriesTypes.flags#drawPoints
  6903. * @return {void}
  6904. */
  6905. drawPoints: function () {
  6906. var series = this,
  6907. points = series.points,
  6908. chart = series.chart,
  6909. renderer = chart.renderer,
  6910. plotX,
  6911. plotY,
  6912. inverted = chart.inverted,
  6913. options = series.options,
  6914. optionsY = options.y,
  6915. shape,
  6916. i,
  6917. point,
  6918. graphic,
  6919. stackIndex,
  6920. anchorY,
  6921. attribs,
  6922. outsideRight,
  6923. yAxis = series.yAxis,
  6924. boxesMap = {},
  6925. boxes = [],
  6926. centered;
  6927. i = points.length;
  6928. while (i--) {
  6929. point = points[i];
  6930. outsideRight =
  6931. (inverted ? point.plotY : point.plotX) >
  6932. series.xAxis.len;
  6933. plotX = point.plotX;
  6934. stackIndex = point.stackIndex;
  6935. shape = point.options.shape || options.shape;
  6936. plotY = point.plotY;
  6937. if (typeof plotY !== 'undefined') {
  6938. plotY = point.plotY + optionsY -
  6939. (typeof stackIndex !== 'undefined' &&
  6940. (stackIndex * options.stackDistance));
  6941. }
  6942. // skip connectors for higher level stacked points
  6943. point.anchorX = stackIndex ? void 0 : point.plotX;
  6944. anchorY = stackIndex ? void 0 : point.plotY;
  6945. centered = shape !== 'flag';
  6946. graphic = point.graphic;
  6947. // Only draw the point if y is defined and the flag is within
  6948. // the visible area
  6949. if (typeof plotY !== 'undefined' &&
  6950. plotX >= 0 &&
  6951. !outsideRight) {
  6952. // Create the flag
  6953. if (!graphic) {
  6954. graphic = point.graphic = renderer.label('', null, null, shape, null, null, options.useHTML);
  6955. if (!chart.styledMode) {
  6956. graphic
  6957. .attr(series.pointAttribs(point))
  6958. .css(merge(options.style, point.style));
  6959. }
  6960. graphic.attr({
  6961. align: centered ? 'center' : 'left',
  6962. width: options.width,
  6963. height: options.height,
  6964. 'text-align': options.textAlign
  6965. })
  6966. .addClass('highcharts-point')
  6967. .add(series.markerGroup);
  6968. // Add reference to the point for tracker (#6303)
  6969. if (point.graphic.div) {
  6970. point.graphic.div.point = point;
  6971. }
  6972. if (!chart.styledMode) {
  6973. graphic.shadow(options.shadow);
  6974. }
  6975. graphic.isNew = true;
  6976. }
  6977. if (plotX > 0) { // #3119
  6978. plotX -= graphic.strokeWidth() % 2; // #4285
  6979. }
  6980. // Plant the flag
  6981. attribs = {
  6982. y: plotY,
  6983. anchorY: anchorY
  6984. };
  6985. if (options.allowOverlapX) {
  6986. attribs.x = plotX;
  6987. attribs.anchorX = point.anchorX;
  6988. }
  6989. graphic.attr({
  6990. text: point.options.title || options.title || 'A'
  6991. })[graphic.isNew ? 'attr' : 'animate'](attribs);
  6992. // Rig for the distribute function
  6993. if (!options.allowOverlapX) {
  6994. if (!boxesMap[point.plotX]) {
  6995. boxesMap[point.plotX] = {
  6996. align: centered ? 0.5 : 0,
  6997. size: graphic.width,
  6998. target: plotX,
  6999. anchorX: plotX
  7000. };
  7001. }
  7002. else {
  7003. boxesMap[point.plotX].size = Math.max(boxesMap[point.plotX].size, graphic.width);
  7004. }
  7005. }
  7006. // Set the tooltip anchor position
  7007. point.tooltipPos = [
  7008. plotX,
  7009. plotY + yAxis.pos - chart.plotTop
  7010. ]; // #6327
  7011. }
  7012. else if (graphic) {
  7013. point.graphic = graphic.destroy();
  7014. }
  7015. }
  7016. // Handle X-dimension overlapping
  7017. if (!options.allowOverlapX) {
  7018. objectEach(boxesMap, function (box) {
  7019. box.plotX = box.anchorX;
  7020. boxes.push(box);
  7021. });
  7022. H.distribute(boxes, inverted ? yAxis.len : this.xAxis.len, 100);
  7023. points.forEach(function (point) {
  7024. var box = point.graphic && boxesMap[point.plotX];
  7025. if (box) {
  7026. point.graphic[point.graphic.isNew ? 'attr' : 'animate']({
  7027. x: box.pos + box.align * box.size,
  7028. anchorX: point.anchorX
  7029. });
  7030. // Hide flag when its box position is not specified
  7031. // (#8573, #9299)
  7032. if (!defined(box.pos)) {
  7033. point.graphic.attr({
  7034. x: -9999,
  7035. anchorX: -9999
  7036. });
  7037. point.graphic.isNew = true;
  7038. }
  7039. else {
  7040. point.graphic.isNew = false;
  7041. }
  7042. }
  7043. });
  7044. }
  7045. // Can be a mix of SVG and HTML and we need events for both (#6303)
  7046. if (options.useHTML) {
  7047. wrap(series.markerGroup, 'on', function (proceed) {
  7048. return SVGElement.prototype.on.apply(
  7049. // for HTML
  7050. proceed.apply(this, [].slice.call(arguments, 1)),
  7051. // and for SVG
  7052. [].slice.call(arguments, 1));
  7053. });
  7054. }
  7055. },
  7056. /**
  7057. * Extend the column trackers with listeners to expand and contract
  7058. * stacks.
  7059. *
  7060. * @private
  7061. * @function Highcharts.seriesTypes.flags#drawTracker
  7062. * @return {void}
  7063. */
  7064. drawTracker: function () {
  7065. var series = this,
  7066. points = series.points;
  7067. TrackerMixin.drawTrackerPoint.apply(this);
  7068. /* *
  7069. * Bring each stacked flag up on mouse over, this allows readability
  7070. * of vertically stacked elements as well as tight points on the x
  7071. * axis. #1924.
  7072. */
  7073. points.forEach(function (point) {
  7074. var graphic = point.graphic;
  7075. if (graphic) {
  7076. addEvent(graphic.element, 'mouseover', function () {
  7077. // Raise this point
  7078. if (point.stackIndex > 0 &&
  7079. !point.raised) {
  7080. point._y = graphic.y;
  7081. graphic.attr({
  7082. y: point._y - 8
  7083. });
  7084. point.raised = true;
  7085. }
  7086. // Revert other raised points
  7087. points.forEach(function (otherPoint) {
  7088. if (otherPoint !== point &&
  7089. otherPoint.raised &&
  7090. otherPoint.graphic) {
  7091. otherPoint.graphic.attr({
  7092. y: otherPoint._y
  7093. });
  7094. otherPoint.raised = false;
  7095. }
  7096. });
  7097. });
  7098. }
  7099. });
  7100. },
  7101. /**
  7102. * Disable animation, but keep clipping (#8546).
  7103. *
  7104. * @private
  7105. * @function Highcharts.seriesTypes.flags#animate
  7106. * @param {boolean} [init]
  7107. * @return {void}
  7108. */
  7109. animate: function (init) {
  7110. if (init) {
  7111. this.setClip();
  7112. }
  7113. },
  7114. /**
  7115. * @private
  7116. * @function Highcharts.seriesTypes.flags#setClip
  7117. * @return {void}
  7118. */
  7119. setClip: function () {
  7120. Series.prototype.setClip.apply(this, arguments);
  7121. if (this.options.clip !== false && this.sharedClipKey) {
  7122. this.markerGroup
  7123. .clip(this.chart[this.sharedClipKey]);
  7124. }
  7125. },
  7126. /**
  7127. * @private
  7128. * @function Highcharts.seriesTypes.flags#buildKDTree
  7129. */
  7130. buildKDTree: noop,
  7131. /**
  7132. * Don't invert the flag marker group (#4960).
  7133. *
  7134. * @private
  7135. * @function Highcharts.seriesTypes.flags#invertGroups
  7136. */
  7137. invertGroups: noop
  7138. /* eslint-enable no-invalid-this, valid-jsdoc */
  7139. },
  7140. /**
  7141. * @lends Highcharts.seriesTypes.flag.prototype.pointClass.prototype
  7142. */
  7143. {
  7144. isValid: function () {
  7145. // #9233 - Prevent from treating flags as null points (even if
  7146. // they have no y values defined).
  7147. return isNumber(this.y) || typeof this.y === 'undefined';
  7148. }
  7149. });
  7150. // create the flag icon with anchor
  7151. symbols.flag = function (x, y, w, h, options) {
  7152. var anchorX = (options && options.anchorX) || x,
  7153. anchorY = (options && options.anchorY) || y;
  7154. // To do: unwanted any cast because symbols.circle has wrong type, it
  7155. // actually returns an SVGPathArray
  7156. var path = symbols.circle(anchorX - 1,
  7157. anchorY - 1, 2, 2);
  7158. path.push(['M', anchorX, anchorY], ['L', x, y + h], ['L', x, y], ['L', x + w, y], ['L', x + w, y + h], ['L', x, y + h], ['Z']);
  7159. return path;
  7160. };
  7161. /**
  7162. * Create the circlepin and squarepin icons with anchor.
  7163. * @private
  7164. * @param {string} shape - circle or square
  7165. * @return {void}
  7166. */
  7167. function createPinSymbol(shape) {
  7168. symbols[shape + 'pin'] = function (x, y, w, h, options) {
  7169. var anchorX = options && options.anchorX,
  7170. anchorY = options && options.anchorY,
  7171. path;
  7172. // For single-letter flags, make sure circular flags are not taller
  7173. // than their width
  7174. if (shape === 'circle' && h > w) {
  7175. x -= Math.round((h - w) / 2);
  7176. w = h;
  7177. }
  7178. path = (symbols[shape])(x, y, w, h);
  7179. if (anchorX && anchorY) {
  7180. /**
  7181. * If the label is below the anchor, draw the connecting line from
  7182. * the top edge of the label, otherwise start drawing from the
  7183. * bottom edge
  7184. */
  7185. var labelX = anchorX;
  7186. if (shape === 'circle') {
  7187. labelX = x + w / 2;
  7188. }
  7189. else {
  7190. var startSeg = path[0];
  7191. var endSeg = path[1];
  7192. if (startSeg[0] === 'M' && endSeg[0] === 'L') {
  7193. labelX = (startSeg[1] + endSeg[1]) / 2;
  7194. }
  7195. }
  7196. var labelY = (y > anchorY) ? y : y + h;
  7197. path.push([
  7198. 'M',
  7199. labelX,
  7200. labelY
  7201. ], [
  7202. 'L',
  7203. anchorX,
  7204. anchorY
  7205. ]);
  7206. path = path.concat(symbols.circle(anchorX - 1, anchorY - 1, 2, 2));
  7207. }
  7208. return path;
  7209. };
  7210. }
  7211. createPinSymbol('circle');
  7212. createPinSymbol('square');
  7213. /**
  7214. * The symbol callbacks are generated on the SVGRenderer object in all browsers.
  7215. * Even VML browsers need this in order to generate shapes in export. Now share
  7216. * them with the VMLRenderer.
  7217. */
  7218. if (Renderer === VMLRenderer) {
  7219. ['circlepin', 'flag', 'squarepin'].forEach(function (shape) {
  7220. VMLRenderer.prototype.symbols[shape] = symbols[shape];
  7221. });
  7222. }
  7223. /**
  7224. * A `flags` series. If the [type](#series.flags.type) option is not
  7225. * specified, it is inherited from [chart.type](#chart.type).
  7226. *
  7227. * @extends series,plotOptions.flags
  7228. * @excluding animation, borderColor, borderRadius, borderWidth, colorByPoint,
  7229. * connectNulls, dashStyle, dataGrouping, dataParser, dataURL,
  7230. * gapSize, gapUnit, linecap, lineWidth, marker, pointPadding,
  7231. * pointWidth, step, turboThreshold, useOhlcData
  7232. * @product highstock
  7233. * @apioption series.flags
  7234. */
  7235. /**
  7236. * An array of data points for the series. For the `flags` series type,
  7237. * points can be given in the following ways:
  7238. *
  7239. * 1. An array of objects with named values. The following snippet shows only a
  7240. * few settings, see the complete options set below. If the total number of
  7241. * data points exceeds the series'
  7242. * [turboThreshold](#series.flags.turboThreshold), this option is not
  7243. * available.
  7244. * ```js
  7245. * data: [{
  7246. * x: 1,
  7247. * title: "A",
  7248. * text: "First event"
  7249. * }, {
  7250. * x: 1,
  7251. * title: "B",
  7252. * text: "Second event"
  7253. * }]
  7254. * ```
  7255. *
  7256. * @type {Array<*>}
  7257. * @extends series.line.data
  7258. * @excluding dataLabels, marker, name, y
  7259. * @product highstock
  7260. * @apioption series.flags.data
  7261. */
  7262. /**
  7263. * The fill color of an individual flag. By default it inherits from
  7264. * the series color.
  7265. *
  7266. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  7267. * @product highstock
  7268. * @apioption series.flags.data.fillColor
  7269. */
  7270. /**
  7271. * The longer text to be shown in the flag's tooltip.
  7272. *
  7273. * @type {string}
  7274. * @product highstock
  7275. * @apioption series.flags.data.text
  7276. */
  7277. /**
  7278. * The short text to be shown on the flag.
  7279. *
  7280. * @type {string}
  7281. * @product highstock
  7282. * @apioption series.flags.data.title
  7283. */
  7284. ''; // adds doclets above to transpiled file
  7285. });
  7286. _registerModule(_modules, 'Extensions/RangeSelector.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/Options.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (Axis, Chart, H, O, SVGElement, U) {
  7287. /* *
  7288. *
  7289. * (c) 2010-2020 Torstein Honsi
  7290. *
  7291. * License: www.highcharts.com/license
  7292. *
  7293. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  7294. *
  7295. * */
  7296. var defaultOptions = O.defaultOptions;
  7297. var addEvent = U.addEvent,
  7298. createElement = U.createElement,
  7299. css = U.css,
  7300. defined = U.defined,
  7301. destroyObjectProperties = U.destroyObjectProperties,
  7302. discardElement = U.discardElement,
  7303. extend = U.extend,
  7304. fireEvent = U.fireEvent,
  7305. isNumber = U.isNumber,
  7306. merge = U.merge,
  7307. objectEach = U.objectEach,
  7308. pick = U.pick,
  7309. pInt = U.pInt,
  7310. splat = U.splat;
  7311. /**
  7312. * Define the time span for the button
  7313. *
  7314. * @typedef {"all"|"day"|"hour"|"millisecond"|"minute"|"month"|"second"|"week"|"year"|"ytd"} Highcharts.RangeSelectorButtonTypeValue
  7315. */
  7316. /**
  7317. * Callback function to react on button clicks.
  7318. *
  7319. * @callback Highcharts.RangeSelectorClickCallbackFunction
  7320. *
  7321. * @param {global.Event} e
  7322. * Event arguments.
  7323. *
  7324. * @param {boolean|undefined}
  7325. * Return false to cancel the default button event.
  7326. */
  7327. /**
  7328. * Callback function to parse values entered in the input boxes and return a
  7329. * valid JavaScript time as milliseconds since 1970.
  7330. *
  7331. * @callback Highcharts.RangeSelectorParseCallbackFunction
  7332. *
  7333. * @param {string} value
  7334. * Input value to parse.
  7335. *
  7336. * @return {number}
  7337. * Parsed JavaScript time value.
  7338. */
  7339. /* ************************************************************************** *
  7340. * Start Range Selector code *
  7341. * ************************************************************************** */
  7342. extend(defaultOptions, {
  7343. /**
  7344. * The range selector is a tool for selecting ranges to display within
  7345. * the chart. It provides buttons to select preconfigured ranges in
  7346. * the chart, like 1 day, 1 week, 1 month etc. It also provides input
  7347. * boxes where min and max dates can be manually input.
  7348. *
  7349. * @product highstock gantt
  7350. * @optionparent rangeSelector
  7351. */
  7352. rangeSelector: {
  7353. /**
  7354. * Whether to enable all buttons from the start. By default buttons are
  7355. * only enabled if the corresponding time range exists on the X axis,
  7356. * but enabling all buttons allows for dynamically loading different
  7357. * time ranges.
  7358. *
  7359. * @sample {highstock} stock/rangeselector/allbuttonsenabled-true/
  7360. * All buttons enabled
  7361. *
  7362. * @type {boolean}
  7363. * @default false
  7364. * @since 2.0.3
  7365. * @apioption rangeSelector.allButtonsEnabled
  7366. */
  7367. /**
  7368. * An array of configuration objects for the buttons.
  7369. *
  7370. * Defaults to:
  7371. * ```js
  7372. * buttons: [{
  7373. * type: 'month',
  7374. * count: 1,
  7375. * text: '1m'
  7376. * }, {
  7377. * type: 'month',
  7378. * count: 3,
  7379. * text: '3m'
  7380. * }, {
  7381. * type: 'month',
  7382. * count: 6,
  7383. * text: '6m'
  7384. * }, {
  7385. * type: 'ytd',
  7386. * text: 'YTD'
  7387. * }, {
  7388. * type: 'year',
  7389. * count: 1,
  7390. * text: '1y'
  7391. * }, {
  7392. * type: 'all',
  7393. * text: 'All'
  7394. * }]
  7395. * ```
  7396. *
  7397. * @sample {highstock} stock/rangeselector/datagrouping/
  7398. * Data grouping by buttons
  7399. *
  7400. * @type {Array<*>}
  7401. * @apioption rangeSelector.buttons
  7402. */
  7403. /**
  7404. * How many units of the defined type the button should span. If `type`
  7405. * is "month" and `count` is 3, the button spans three months.
  7406. *
  7407. * @type {number}
  7408. * @default 1
  7409. * @apioption rangeSelector.buttons.count
  7410. */
  7411. /**
  7412. * Fires when clicking on the rangeSelector button. One parameter,
  7413. * event, is passed to the function, containing common event
  7414. * information.
  7415. *
  7416. * ```js
  7417. * click: function(e) {
  7418. * console.log(this);
  7419. * }
  7420. * ```
  7421. *
  7422. * Return false to stop default button's click action.
  7423. *
  7424. * @sample {highstock} stock/rangeselector/button-click/
  7425. * Click event on the button
  7426. *
  7427. * @type {Highcharts.RangeSelectorClickCallbackFunction}
  7428. * @apioption rangeSelector.buttons.events.click
  7429. */
  7430. /**
  7431. * Additional range (in milliseconds) added to the end of the calculated
  7432. * time span.
  7433. *
  7434. * @sample {highstock} stock/rangeselector/min-max-offsets/
  7435. * Button offsets
  7436. *
  7437. * @type {number}
  7438. * @default 0
  7439. * @since 6.0.0
  7440. * @apioption rangeSelector.buttons.offsetMax
  7441. */
  7442. /**
  7443. * Additional range (in milliseconds) added to the start of the
  7444. * calculated time span.
  7445. *
  7446. * @sample {highstock} stock/rangeselector/min-max-offsets/
  7447. * Button offsets
  7448. *
  7449. * @type {number}
  7450. * @default 0
  7451. * @since 6.0.0
  7452. * @apioption rangeSelector.buttons.offsetMin
  7453. */
  7454. /**
  7455. * When buttons apply dataGrouping on a series, by default zooming
  7456. * in/out will deselect buttons and unset dataGrouping. Enable this
  7457. * option to keep buttons selected when extremes change.
  7458. *
  7459. * @sample {highstock} stock/rangeselector/preserve-datagrouping/
  7460. * Different preserveDataGrouping settings
  7461. *
  7462. * @type {boolean}
  7463. * @default false
  7464. * @since 6.1.2
  7465. * @apioption rangeSelector.buttons.preserveDataGrouping
  7466. */
  7467. /**
  7468. * A custom data grouping object for each button.
  7469. *
  7470. * @see [series.dataGrouping](#plotOptions.series.dataGrouping)
  7471. *
  7472. * @sample {highstock} stock/rangeselector/datagrouping/
  7473. * Data grouping by range selector buttons
  7474. *
  7475. * @type {*}
  7476. * @extends plotOptions.series.dataGrouping
  7477. * @apioption rangeSelector.buttons.dataGrouping
  7478. */
  7479. /**
  7480. * The text for the button itself.
  7481. *
  7482. * @type {string}
  7483. * @apioption rangeSelector.buttons.text
  7484. */
  7485. /**
  7486. * Defined the time span for the button. Can be one of `millisecond`,
  7487. * `second`, `minute`, `hour`, `day`, `week`, `month`, `year`, `ytd`,
  7488. * and `all`.
  7489. *
  7490. * @type {Highcharts.RangeSelectorButtonTypeValue}
  7491. * @apioption rangeSelector.buttons.type
  7492. */
  7493. /**
  7494. * The space in pixels between the buttons in the range selector.
  7495. *
  7496. * @type {number}
  7497. * @default 0
  7498. * @apioption rangeSelector.buttonSpacing
  7499. */
  7500. /**
  7501. * Enable or disable the range selector.
  7502. *
  7503. * @sample {highstock} stock/rangeselector/enabled/
  7504. * Disable the range selector
  7505. *
  7506. * @type {boolean}
  7507. * @default true
  7508. * @apioption rangeSelector.enabled
  7509. */
  7510. /**
  7511. * The vertical alignment of the rangeselector box. Allowed properties
  7512. * are `top`, `middle`, `bottom`.
  7513. *
  7514. * @sample {highstock} stock/rangeselector/vertical-align-middle/
  7515. * Middle
  7516. * @sample {highstock} stock/rangeselector/vertical-align-bottom/
  7517. * Bottom
  7518. *
  7519. * @type {Highcharts.VerticalAlignValue}
  7520. * @since 6.0.0
  7521. */
  7522. verticalAlign: 'top',
  7523. /**
  7524. * A collection of attributes for the buttons. The object takes SVG
  7525. * attributes like `fill`, `stroke`, `stroke-width`, as well as `style`,
  7526. * a collection of CSS properties for the text.
  7527. *
  7528. * The object can also be extended with states, so you can set
  7529. * presentational options for `hover`, `select` or `disabled` button
  7530. * states.
  7531. *
  7532. * CSS styles for the text label.
  7533. *
  7534. * In styled mode, the buttons are styled by the
  7535. * `.highcharts-range-selector-buttons .highcharts-button` rule with its
  7536. * different states.
  7537. *
  7538. * @sample {highstock} stock/rangeselector/styling/
  7539. * Styling the buttons and inputs
  7540. *
  7541. * @type {Highcharts.SVGAttributes}
  7542. */
  7543. buttonTheme: {
  7544. /** @ignore */
  7545. width: 28,
  7546. /** @ignore */
  7547. height: 18,
  7548. /** @ignore */
  7549. padding: 2,
  7550. /** @ignore */
  7551. zIndex: 7 // #484, #852
  7552. },
  7553. /**
  7554. * When the rangeselector is floating, the plot area does not reserve
  7555. * space for it. This opens for positioning anywhere on the chart.
  7556. *
  7557. * @sample {highstock} stock/rangeselector/floating/
  7558. * Placing the range selector between the plot area and the
  7559. * navigator
  7560. *
  7561. * @since 6.0.0
  7562. */
  7563. floating: false,
  7564. /**
  7565. * The x offset of the range selector relative to its horizontal
  7566. * alignment within `chart.spacingLeft` and `chart.spacingRight`.
  7567. *
  7568. * @since 6.0.0
  7569. */
  7570. x: 0,
  7571. /**
  7572. * The y offset of the range selector relative to its horizontal
  7573. * alignment within `chart.spacingLeft` and `chart.spacingRight`.
  7574. *
  7575. * @since 6.0.0
  7576. */
  7577. y: 0,
  7578. /**
  7579. * Deprecated. The height of the range selector. Currently it is
  7580. * calculated dynamically.
  7581. *
  7582. * @deprecated
  7583. * @type {number|undefined}
  7584. * @since 2.1.9
  7585. */
  7586. height: void 0,
  7587. /**
  7588. * The border color of the date input boxes.
  7589. *
  7590. * @sample {highstock} stock/rangeselector/styling/
  7591. * Styling the buttons and inputs
  7592. *
  7593. * @type {Highcharts.ColorString}
  7594. * @default #cccccc
  7595. * @since 1.3.7
  7596. * @apioption rangeSelector.inputBoxBorderColor
  7597. */
  7598. /**
  7599. * The pixel height of the date input boxes.
  7600. *
  7601. * @sample {highstock} stock/rangeselector/styling/
  7602. * Styling the buttons and inputs
  7603. *
  7604. * @type {number}
  7605. * @default 17
  7606. * @since 1.3.7
  7607. * @apioption rangeSelector.inputBoxHeight
  7608. */
  7609. /**
  7610. * CSS for the container DIV holding the input boxes. Deprecated as
  7611. * of 1.2.5\. Use [inputPosition](#rangeSelector.inputPosition) instead.
  7612. *
  7613. * @sample {highstock} stock/rangeselector/styling/
  7614. * Styling the buttons and inputs
  7615. *
  7616. * @deprecated
  7617. * @type {Highcharts.CSSObject}
  7618. * @apioption rangeSelector.inputBoxStyle
  7619. */
  7620. /**
  7621. * The pixel width of the date input boxes.
  7622. *
  7623. * @sample {highstock} stock/rangeselector/styling/
  7624. * Styling the buttons and inputs
  7625. *
  7626. * @type {number}
  7627. * @default 90
  7628. * @since 1.3.7
  7629. * @apioption rangeSelector.inputBoxWidth
  7630. */
  7631. /**
  7632. * The date format in the input boxes when not selected for editing.
  7633. * Defaults to `%b %e, %Y`.
  7634. *
  7635. * @sample {highstock} stock/rangeselector/input-format/
  7636. * Milliseconds in the range selector
  7637. *
  7638. * @type {string}
  7639. * @default %b %e, %Y
  7640. * @apioption rangeSelector.inputDateFormat
  7641. */
  7642. /**
  7643. * A custom callback function to parse values entered in the input boxes
  7644. * and return a valid JavaScript time as milliseconds since 1970.
  7645. * The first argument passed is a value to parse,
  7646. * second is a boolean indicating use of the UTC time.
  7647. *
  7648. * @sample {highstock} stock/rangeselector/input-format/
  7649. * Milliseconds in the range selector
  7650. *
  7651. * @type {Highcharts.RangeSelectorParseCallbackFunction}
  7652. * @since 1.3.3
  7653. * @apioption rangeSelector.inputDateParser
  7654. */
  7655. /**
  7656. * The date format in the input boxes when they are selected for
  7657. * editing. This must be a format that is recognized by JavaScript
  7658. * Date.parse.
  7659. *
  7660. * @sample {highstock} stock/rangeselector/input-format/
  7661. * Milliseconds in the range selector
  7662. *
  7663. * @type {string}
  7664. * @default %Y-%m-%d
  7665. * @apioption rangeSelector.inputEditDateFormat
  7666. */
  7667. /**
  7668. * Enable or disable the date input boxes. Defaults to enabled when
  7669. * there is enough space, disabled if not (typically mobile).
  7670. *
  7671. * @sample {highstock} stock/rangeselector/input-datepicker/
  7672. * Extending the input with a jQuery UI datepicker
  7673. *
  7674. * @type {boolean}
  7675. * @default true
  7676. * @apioption rangeSelector.inputEnabled
  7677. */
  7678. /**
  7679. * Positioning for the input boxes. Allowed properties are `align`,
  7680. * `x` and `y`.
  7681. *
  7682. * @since 1.2.4
  7683. */
  7684. inputPosition: {
  7685. /**
  7686. * The alignment of the input box. Allowed properties are `left`,
  7687. * `center`, `right`.
  7688. *
  7689. * @sample {highstock} stock/rangeselector/input-button-position/
  7690. * Alignment
  7691. *
  7692. * @type {Highcharts.AlignValue}
  7693. * @since 6.0.0
  7694. */
  7695. align: 'right',
  7696. /**
  7697. * X offset of the input row.
  7698. */
  7699. x: 0,
  7700. /**
  7701. * Y offset of the input row.
  7702. */
  7703. y: 0
  7704. },
  7705. /**
  7706. * The index of the button to appear pre-selected.
  7707. *
  7708. * @type {number}
  7709. * @apioption rangeSelector.selected
  7710. */
  7711. /**
  7712. * Positioning for the button row.
  7713. *
  7714. * @since 1.2.4
  7715. */
  7716. buttonPosition: {
  7717. /**
  7718. * The alignment of the input box. Allowed properties are `left`,
  7719. * `center`, `right`.
  7720. *
  7721. * @sample {highstock} stock/rangeselector/input-button-position/
  7722. * Alignment
  7723. *
  7724. * @type {Highcharts.AlignValue}
  7725. * @since 6.0.0
  7726. */
  7727. align: 'left',
  7728. /**
  7729. * X offset of the button row.
  7730. */
  7731. x: 0,
  7732. /**
  7733. * Y offset of the button row.
  7734. */
  7735. y: 0
  7736. },
  7737. /**
  7738. * CSS for the HTML inputs in the range selector.
  7739. *
  7740. * In styled mode, the inputs are styled by the
  7741. * `.highcharts-range-input text` rule in SVG mode, and
  7742. * `input.highcharts-range-selector` when active.
  7743. *
  7744. * @sample {highstock} stock/rangeselector/styling/
  7745. * Styling the buttons and inputs
  7746. *
  7747. * @type {Highcharts.CSSObject}
  7748. * @apioption rangeSelector.inputStyle
  7749. */
  7750. /**
  7751. * CSS styles for the labels - the Zoom, From and To texts.
  7752. *
  7753. * In styled mode, the labels are styled by the
  7754. * `.highcharts-range-label` class.
  7755. *
  7756. * @sample {highstock} stock/rangeselector/styling/
  7757. * Styling the buttons and inputs
  7758. *
  7759. * @type {Highcharts.CSSObject}
  7760. */
  7761. labelStyle: {
  7762. /** @ignore */
  7763. color: '#666666'
  7764. }
  7765. }
  7766. });
  7767. defaultOptions.lang = merge(defaultOptions.lang,
  7768. /**
  7769. * Language object. The language object is global and it can't be set
  7770. * on each chart initialization. Instead, use `Highcharts.setOptions` to
  7771. * set it before any chart is initialized.
  7772. *
  7773. * ```js
  7774. * Highcharts.setOptions({
  7775. * lang: {
  7776. * months: [
  7777. * 'Janvier', 'Février', 'Mars', 'Avril',
  7778. * 'Mai', 'Juin', 'Juillet', 'Août',
  7779. * 'Septembre', 'Octobre', 'Novembre', 'Décembre'
  7780. * ],
  7781. * weekdays: [
  7782. * 'Dimanche', 'Lundi', 'Mardi', 'Mercredi',
  7783. * 'Jeudi', 'Vendredi', 'Samedi'
  7784. * ]
  7785. * }
  7786. * });
  7787. * ```
  7788. *
  7789. * @optionparent lang
  7790. */
  7791. {
  7792. /**
  7793. * The text for the label for the range selector buttons.
  7794. *
  7795. * @product highstock gantt
  7796. */
  7797. rangeSelectorZoom: 'Zoom',
  7798. /**
  7799. * The text for the label for the "from" input box in the range
  7800. * selector.
  7801. *
  7802. * @product highstock gantt
  7803. */
  7804. rangeSelectorFrom: 'From',
  7805. /**
  7806. * The text for the label for the "to" input box in the range selector.
  7807. *
  7808. * @product highstock gantt
  7809. */
  7810. rangeSelectorTo: 'To'
  7811. });
  7812. /* eslint-disable no-invalid-this, valid-jsdoc */
  7813. /**
  7814. * The range selector.
  7815. *
  7816. * @private
  7817. * @class
  7818. * @name Highcharts.RangeSelector
  7819. * @param {Highcharts.Chart} chart
  7820. */
  7821. var RangeSelector = /** @class */ (function () {
  7822. function RangeSelector(chart) {
  7823. /* *
  7824. *
  7825. * Properties
  7826. *
  7827. * */
  7828. this.buttons = void 0;
  7829. this.buttonOptions = RangeSelector.prototype.defaultButtons;
  7830. this.options = void 0;
  7831. this.chart = chart;
  7832. // Run RangeSelector
  7833. this.init(chart);
  7834. }
  7835. /**
  7836. * The method to run when one of the buttons in the range selectors is
  7837. * clicked
  7838. *
  7839. * @private
  7840. * @function Highcharts.RangeSelector#clickButton
  7841. * @param {number} i
  7842. * The index of the button
  7843. * @param {boolean} [redraw]
  7844. * @return {void}
  7845. */
  7846. RangeSelector.prototype.clickButton = function (i, redraw) {
  7847. var rangeSelector = this,
  7848. chart = rangeSelector.chart,
  7849. rangeOptions = rangeSelector.buttonOptions[i],
  7850. baseAxis = chart.xAxis[0],
  7851. unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis || {},
  7852. dataMin = unionExtremes.dataMin,
  7853. dataMax = unionExtremes.dataMax,
  7854. newMin,
  7855. newMax = baseAxis && Math.round(Math.min(baseAxis.max,
  7856. pick(dataMax,
  7857. baseAxis.max))), // #1568
  7858. type = rangeOptions.type,
  7859. baseXAxisOptions,
  7860. range = rangeOptions._range,
  7861. rangeMin,
  7862. minSetting,
  7863. rangeSetting,
  7864. ctx,
  7865. ytdExtremes,
  7866. dataGrouping = rangeOptions.dataGrouping;
  7867. // chart has no data, base series is removed
  7868. if (dataMin === null || dataMax === null) {
  7869. return;
  7870. }
  7871. // Set the fixed range before range is altered
  7872. chart.fixedRange = range;
  7873. // Apply dataGrouping associated to button
  7874. if (dataGrouping) {
  7875. this.forcedDataGrouping = true;
  7876. Axis.prototype.setDataGrouping.call(baseAxis || { chart: this.chart }, dataGrouping, false);
  7877. this.frozenStates = rangeOptions.preserveDataGrouping;
  7878. }
  7879. // Apply range
  7880. if (type === 'month' || type === 'year') {
  7881. if (!baseAxis) {
  7882. // This is set to the user options and picked up later when the
  7883. // axis is instantiated so that we know the min and max.
  7884. range = rangeOptions;
  7885. }
  7886. else {
  7887. ctx = {
  7888. range: rangeOptions,
  7889. max: newMax,
  7890. chart: chart,
  7891. dataMin: dataMin,
  7892. dataMax: dataMax
  7893. };
  7894. newMin = baseAxis.minFromRange.call(ctx);
  7895. if (isNumber(ctx.newMax)) {
  7896. newMax = ctx.newMax;
  7897. }
  7898. }
  7899. // Fixed times like minutes, hours, days
  7900. }
  7901. else if (range) {
  7902. newMin = Math.max(newMax - range, dataMin);
  7903. newMax = Math.min(newMin + range, dataMax);
  7904. }
  7905. else if (type === 'ytd') {
  7906. // On user clicks on the buttons, or a delayed action running from
  7907. // the beforeRender event (below), the baseAxis is defined.
  7908. if (baseAxis) {
  7909. // When "ytd" is the pre-selected button for the initial view,
  7910. // its calculation is delayed and rerun in the beforeRender
  7911. // event (below). When the series are initialized, but before
  7912. // the chart is rendered, we have access to the xData array
  7913. // (#942).
  7914. if (typeof dataMax === 'undefined') {
  7915. dataMin = Number.MAX_VALUE;
  7916. dataMax = Number.MIN_VALUE;
  7917. chart.series.forEach(function (series) {
  7918. // reassign it to the last item
  7919. var xData = series.xData;
  7920. dataMin = Math.min(xData[0], dataMin);
  7921. dataMax = Math.max(xData[xData.length - 1], dataMax);
  7922. });
  7923. redraw = false;
  7924. }
  7925. ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, chart.time.useUTC);
  7926. newMin = rangeMin = ytdExtremes.min;
  7927. newMax = ytdExtremes.max;
  7928. // "ytd" is pre-selected. We don't yet have access to processed
  7929. // point and extremes data (things like pointStart and pointInterval
  7930. // are missing), so we delay the process (#942)
  7931. }
  7932. else {
  7933. rangeSelector.deferredYTDClick = i;
  7934. return;
  7935. }
  7936. }
  7937. else if (type === 'all' && baseAxis) {
  7938. newMin = dataMin;
  7939. newMax = dataMax;
  7940. }
  7941. if (defined(newMin)) {
  7942. newMin += rangeOptions._offsetMin;
  7943. }
  7944. if (defined(newMax)) {
  7945. newMax += rangeOptions._offsetMax;
  7946. }
  7947. rangeSelector.setSelected(i);
  7948. // Update the chart
  7949. if (!baseAxis) {
  7950. // Axis not yet instanciated. Temporarily set min and range
  7951. // options and remove them on chart load (#4317).
  7952. baseXAxisOptions = splat(chart.options.xAxis)[0];
  7953. rangeSetting = baseXAxisOptions.range;
  7954. baseXAxisOptions.range = range;
  7955. minSetting = baseXAxisOptions.min;
  7956. baseXAxisOptions.min = rangeMin;
  7957. addEvent(chart, 'load', function resetMinAndRange() {
  7958. baseXAxisOptions.range = rangeSetting;
  7959. baseXAxisOptions.min = minSetting;
  7960. });
  7961. }
  7962. else {
  7963. // Existing axis object. Set extremes after render time.
  7964. baseAxis.setExtremes(newMin, newMax, pick(redraw, 1), null, // auto animation
  7965. {
  7966. trigger: 'rangeSelectorButton',
  7967. rangeSelectorButton: rangeOptions
  7968. });
  7969. }
  7970. };
  7971. /**
  7972. * Set the selected option. This method only sets the internal flag, it
  7973. * doesn't update the buttons or the actual zoomed range.
  7974. *
  7975. * @private
  7976. * @function Highcharts.RangeSelector#setSelected
  7977. * @param {number} [selected]
  7978. * @return {void}
  7979. */
  7980. RangeSelector.prototype.setSelected = function (selected) {
  7981. this.selected = this.options.selected = selected;
  7982. };
  7983. /**
  7984. * Initialize the range selector
  7985. *
  7986. * @private
  7987. * @function Highcharts.RangeSelector#init
  7988. * @param {Highcharts.Chart} chart
  7989. * @return {void}
  7990. */
  7991. RangeSelector.prototype.init = function (chart) {
  7992. var rangeSelector = this,
  7993. options = chart.options.rangeSelector,
  7994. buttonOptions = options.buttons || rangeSelector.defaultButtons.slice(),
  7995. selectedOption = options.selected,
  7996. blurInputs = function () {
  7997. var minInput = rangeSelector.minInput,
  7998. maxInput = rangeSelector.maxInput;
  7999. // #3274 in some case blur is not defined
  8000. if (minInput && minInput.blur) {
  8001. fireEvent(minInput, 'blur');
  8002. }
  8003. if (maxInput && maxInput.blur) {
  8004. fireEvent(maxInput, 'blur');
  8005. }
  8006. };
  8007. rangeSelector.chart = chart;
  8008. rangeSelector.options = options;
  8009. rangeSelector.buttons = [];
  8010. rangeSelector.buttonOptions = buttonOptions;
  8011. this.unMouseDown = addEvent(chart.container, 'mousedown', blurInputs);
  8012. this.unResize = addEvent(chart, 'resize', blurInputs);
  8013. // Extend the buttonOptions with actual range
  8014. buttonOptions.forEach(rangeSelector.computeButtonRange);
  8015. // zoomed range based on a pre-selected button index
  8016. if (typeof selectedOption !== 'undefined' &&
  8017. buttonOptions[selectedOption]) {
  8018. this.clickButton(selectedOption, false);
  8019. }
  8020. addEvent(chart, 'load', function () {
  8021. // If a data grouping is applied to the current button, release it
  8022. // when extremes change
  8023. if (chart.xAxis && chart.xAxis[0]) {
  8024. addEvent(chart.xAxis[0], 'setExtremes', function (e) {
  8025. if (this.max - this.min !==
  8026. chart.fixedRange &&
  8027. e.trigger !== 'rangeSelectorButton' &&
  8028. e.trigger !== 'updatedData' &&
  8029. rangeSelector.forcedDataGrouping &&
  8030. !rangeSelector.frozenStates) {
  8031. this.setDataGrouping(false, false);
  8032. }
  8033. });
  8034. }
  8035. });
  8036. };
  8037. /**
  8038. * Dynamically update the range selector buttons after a new range has been
  8039. * set
  8040. *
  8041. * @private
  8042. * @function Highcharts.RangeSelector#updateButtonStates
  8043. * @return {void}
  8044. */
  8045. RangeSelector.prototype.updateButtonStates = function () {
  8046. var rangeSelector = this,
  8047. chart = this.chart,
  8048. baseAxis = chart.xAxis[0],
  8049. actualRange = Math.round(baseAxis.max - baseAxis.min),
  8050. hasNoData = !baseAxis.hasVisibleSeries,
  8051. day = 24 * 36e5, // A single day in milliseconds
  8052. unionExtremes = (chart.scroller &&
  8053. chart.scroller.getUnionExtremes()) || baseAxis,
  8054. dataMin = unionExtremes.dataMin,
  8055. dataMax = unionExtremes.dataMax,
  8056. ytdExtremes = rangeSelector.getYTDExtremes(dataMax,
  8057. dataMin,
  8058. chart.time.useUTC),
  8059. ytdMin = ytdExtremes.min,
  8060. ytdMax = ytdExtremes.max,
  8061. selected = rangeSelector.selected,
  8062. selectedExists = isNumber(selected),
  8063. allButtonsEnabled = rangeSelector.options.allButtonsEnabled,
  8064. buttons = rangeSelector.buttons;
  8065. rangeSelector.buttonOptions.forEach(function (rangeOptions, i) {
  8066. var range = rangeOptions._range,
  8067. type = rangeOptions.type,
  8068. count = rangeOptions.count || 1,
  8069. button = buttons[i],
  8070. state = 0,
  8071. disable,
  8072. select,
  8073. offsetRange = rangeOptions._offsetMax -
  8074. rangeOptions._offsetMin,
  8075. isSelected = i === selected,
  8076. // Disable buttons where the range exceeds what is allowed in
  8077. // the current view
  8078. isTooGreatRange = range >
  8079. dataMax - dataMin,
  8080. // Disable buttons where the range is smaller than the minimum
  8081. // range
  8082. isTooSmallRange = range < baseAxis.minRange,
  8083. // Do not select the YTD button if not explicitly told so
  8084. isYTDButNotSelected = false,
  8085. // Disable the All button if we're already showing all
  8086. isAllButAlreadyShowingAll = false,
  8087. isSameRange = range === actualRange;
  8088. // Months and years have a variable range so we check the extremes
  8089. if ((type === 'month' || type === 'year') &&
  8090. (actualRange + 36e5 >=
  8091. { month: 28, year: 365 }[type] * day * count - offsetRange) &&
  8092. (actualRange - 36e5 <=
  8093. { month: 31, year: 366 }[type] * day * count + offsetRange)) {
  8094. isSameRange = true;
  8095. }
  8096. else if (type === 'ytd') {
  8097. isSameRange = (ytdMax - ytdMin + offsetRange) === actualRange;
  8098. isYTDButNotSelected = !isSelected;
  8099. }
  8100. else if (type === 'all') {
  8101. isSameRange = (baseAxis.max - baseAxis.min >=
  8102. dataMax - dataMin);
  8103. isAllButAlreadyShowingAll = (!isSelected &&
  8104. selectedExists &&
  8105. isSameRange);
  8106. }
  8107. // The new zoom area happens to match the range for a button - mark
  8108. // it selected. This happens when scrolling across an ordinal gap.
  8109. // It can be seen in the intraday demos when selecting 1h and scroll
  8110. // across the night gap.
  8111. disable = (!allButtonsEnabled &&
  8112. (isTooGreatRange ||
  8113. isTooSmallRange ||
  8114. isAllButAlreadyShowingAll ||
  8115. hasNoData));
  8116. select = ((isSelected && isSameRange) ||
  8117. (isSameRange && !selectedExists && !isYTDButNotSelected) ||
  8118. (isSelected && rangeSelector.frozenStates));
  8119. if (disable) {
  8120. state = 3;
  8121. }
  8122. else if (select) {
  8123. selectedExists = true; // Only one button can be selected
  8124. state = 2;
  8125. }
  8126. // If state has changed, update the button
  8127. if (button.state !== state) {
  8128. button.setState(state);
  8129. // Reset (#9209)
  8130. if (state === 0 && selected === i) {
  8131. rangeSelector.setSelected(null);
  8132. }
  8133. }
  8134. });
  8135. };
  8136. /**
  8137. * Compute and cache the range for an individual button
  8138. *
  8139. * @private
  8140. * @function Highcharts.RangeSelector#computeButtonRange
  8141. * @param {Highcharts.RangeSelectorButtonsOptions} rangeOptions
  8142. * @return {void}
  8143. */
  8144. RangeSelector.prototype.computeButtonRange = function (rangeOptions) {
  8145. var type = rangeOptions.type,
  8146. count = rangeOptions.count || 1,
  8147. // these time intervals have a fixed number of milliseconds, as
  8148. // opposed to month, ytd and year
  8149. fixedTimes = {
  8150. millisecond: 1,
  8151. second: 1000,
  8152. minute: 60 * 1000,
  8153. hour: 3600 * 1000,
  8154. day: 24 * 3600 * 1000,
  8155. week: 7 * 24 * 3600 * 1000
  8156. };
  8157. // Store the range on the button object
  8158. if (fixedTimes[type]) {
  8159. rangeOptions._range = fixedTimes[type] * count;
  8160. }
  8161. else if (type === 'month' || type === 'year') {
  8162. rangeOptions._range = {
  8163. month: 30,
  8164. year: 365
  8165. }[type] * 24 * 36e5 * count;
  8166. }
  8167. rangeOptions._offsetMin = pick(rangeOptions.offsetMin, 0);
  8168. rangeOptions._offsetMax = pick(rangeOptions.offsetMax, 0);
  8169. rangeOptions._range +=
  8170. rangeOptions._offsetMax - rangeOptions._offsetMin;
  8171. };
  8172. /**
  8173. * Set the internal and displayed value of a HTML input for the dates
  8174. *
  8175. * @private
  8176. * @function Highcharts.RangeSelector#setInputValue
  8177. * @param {string} name
  8178. * @param {number} [inputTime]
  8179. * @return {void}
  8180. */
  8181. RangeSelector.prototype.setInputValue = function (name, inputTime) {
  8182. var options = this.chart.options.rangeSelector,
  8183. time = this.chart.time,
  8184. input = this[name + 'Input'];
  8185. if (defined(inputTime)) {
  8186. input.previousValue = input.HCTime;
  8187. input.HCTime = inputTime;
  8188. }
  8189. input.value = time.dateFormat(options.inputEditDateFormat || '%Y-%m-%d', input.HCTime);
  8190. this[name + 'DateBox'].attr({
  8191. text: time.dateFormat(options.inputDateFormat || '%b %e, %Y', input.HCTime)
  8192. });
  8193. };
  8194. /**
  8195. * @private
  8196. * @function Highcharts.RangeSelector#showInput
  8197. * @param {string} name
  8198. * @return {void}
  8199. */
  8200. RangeSelector.prototype.showInput = function (name) {
  8201. var inputGroup = this.inputGroup,
  8202. dateBox = this[name + 'DateBox'];
  8203. css(this[name + 'Input'], {
  8204. left: (inputGroup.translateX + dateBox.x) + 'px',
  8205. top: inputGroup.translateY + 'px',
  8206. width: (dateBox.width - 2) + 'px',
  8207. height: (dateBox.height - 2) + 'px',
  8208. border: '2px solid silver'
  8209. });
  8210. };
  8211. /**
  8212. * @private
  8213. * @function Highcharts.RangeSelector#hideInput
  8214. * @param {string} name
  8215. * @return {void}
  8216. */
  8217. RangeSelector.prototype.hideInput = function (name) {
  8218. css(this[name + 'Input'], {
  8219. border: 0,
  8220. width: '1px',
  8221. height: '1px'
  8222. });
  8223. this.setInputValue(name);
  8224. };
  8225. /**
  8226. * @private
  8227. * @function Highcharts.RangeSelector#defaultInputDateParser
  8228. */
  8229. RangeSelector.prototype.defaultInputDateParser = function (inputDate, useUTC) {
  8230. var date = new Date();
  8231. if (H.isSafari) {
  8232. return Date.parse(inputDate.split(' ').join('T'));
  8233. }
  8234. if (useUTC) {
  8235. return Date.parse(inputDate + 'Z');
  8236. }
  8237. return Date.parse(inputDate) - date.getTimezoneOffset() * 60 * 1000;
  8238. };
  8239. /**
  8240. * Draw either the 'from' or the 'to' HTML input box of the range selector
  8241. *
  8242. * @private
  8243. * @function Highcharts.RangeSelector#drawInput
  8244. * @param {string} name
  8245. * @return {void}
  8246. */
  8247. RangeSelector.prototype.drawInput = function (name) {
  8248. var rangeSelector = this,
  8249. chart = rangeSelector.chart,
  8250. chartStyle = chart.renderer.style || {},
  8251. renderer = chart.renderer,
  8252. options = chart.options.rangeSelector,
  8253. lang = defaultOptions.lang,
  8254. div = rangeSelector.div,
  8255. isMin = name === 'min',
  8256. input,
  8257. label,
  8258. dateBox,
  8259. inputGroup = this.inputGroup,
  8260. defaultInputDateParser = this.defaultInputDateParser;
  8261. /**
  8262. * @private
  8263. */
  8264. function updateExtremes() {
  8265. var inputValue = input.value,
  8266. value,
  8267. chartAxis = chart.xAxis[0],
  8268. dataAxis = chart.scroller && chart.scroller.xAxis ?
  8269. chart.scroller.xAxis :
  8270. chartAxis,
  8271. dataMin = dataAxis.dataMin,
  8272. dataMax = dataAxis.dataMax;
  8273. value = (options.inputDateParser || defaultInputDateParser)(inputValue, chart.time.useUTC);
  8274. if (value !== input.previousValue) {
  8275. input.previousValue = value;
  8276. // If the value isn't parsed directly to a value by the
  8277. // browser's Date.parse method, like YYYY-MM-DD in IE, try
  8278. // parsing it a different way
  8279. if (!isNumber(value)) {
  8280. value = inputValue.split('-');
  8281. value = Date.UTC(pInt(value[0]), pInt(value[1]) - 1, pInt(value[2]));
  8282. }
  8283. if (isNumber(value)) {
  8284. // Correct for timezone offset (#433)
  8285. if (!chart.time.useUTC) {
  8286. value =
  8287. value + new Date().getTimezoneOffset() * 60 * 1000;
  8288. }
  8289. // Validate the extremes. If it goes beyound the data min or
  8290. // max, use the actual data extreme (#2438).
  8291. if (isMin) {
  8292. if (value > rangeSelector.maxInput.HCTime) {
  8293. value = void 0;
  8294. }
  8295. else if (value < dataMin) {
  8296. value = dataMin;
  8297. }
  8298. }
  8299. else {
  8300. if (value < rangeSelector.minInput.HCTime) {
  8301. value = void 0;
  8302. }
  8303. else if (value > dataMax) {
  8304. value = dataMax;
  8305. }
  8306. }
  8307. // Set the extremes
  8308. if (typeof value !== 'undefined') { // @todo typof undefined
  8309. chartAxis.setExtremes(isMin ? value : chartAxis.min, isMin ? chartAxis.max : value, void 0, void 0, { trigger: 'rangeSelectorInput' });
  8310. }
  8311. }
  8312. }
  8313. }
  8314. // Create the text label
  8315. this[name + 'Label'] = label = renderer
  8316. .label(lang[isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo'], this.inputGroup.offset)
  8317. .addClass('highcharts-range-label')
  8318. .attr({
  8319. padding: 2
  8320. })
  8321. .add(inputGroup);
  8322. inputGroup.offset += label.width + 5;
  8323. // Create an SVG label that shows updated date ranges and and records
  8324. // click events that bring in the HTML input.
  8325. this[name + 'DateBox'] = dateBox = renderer
  8326. .label('', inputGroup.offset)
  8327. .addClass('highcharts-range-input')
  8328. .attr({
  8329. padding: 2,
  8330. width: options.inputBoxWidth || 90,
  8331. height: options.inputBoxHeight || 17,
  8332. 'text-align': 'center'
  8333. })
  8334. .on('click', function () {
  8335. // If it is already focused, the onfocus event doesn't fire
  8336. // (#3713)
  8337. rangeSelector.showInput(name);
  8338. rangeSelector[name + 'Input'].focus();
  8339. });
  8340. if (!chart.styledMode) {
  8341. dateBox.attr({
  8342. stroke: options.inputBoxBorderColor || '#cccccc',
  8343. 'stroke-width': 1
  8344. });
  8345. }
  8346. dateBox.add(inputGroup);
  8347. inputGroup.offset += dateBox.width + (isMin ? 10 : 0);
  8348. // Create the HTML input element. This is rendered as 1x1 pixel then set
  8349. // to the right size when focused.
  8350. this[name + 'Input'] = input = createElement('input', {
  8351. name: name,
  8352. className: 'highcharts-range-selector',
  8353. type: 'text'
  8354. }, {
  8355. top: chart.plotTop + 'px' // prevent jump on focus in Firefox
  8356. }, div);
  8357. if (!chart.styledMode) {
  8358. // Styles
  8359. label.css(merge(chartStyle, options.labelStyle));
  8360. dateBox.css(merge({
  8361. color: '#333333'
  8362. }, chartStyle, options.inputStyle));
  8363. css(input, extend({
  8364. position: 'absolute',
  8365. border: 0,
  8366. width: '1px',
  8367. height: '1px',
  8368. padding: 0,
  8369. textAlign: 'center',
  8370. fontSize: chartStyle.fontSize,
  8371. fontFamily: chartStyle.fontFamily,
  8372. top: '-9999em' // #4798
  8373. }, options.inputStyle));
  8374. }
  8375. // Blow up the input box
  8376. input.onfocus = function () {
  8377. rangeSelector.showInput(name);
  8378. };
  8379. // Hide away the input box
  8380. input.onblur = function () {
  8381. // update extermes only when inputs are active
  8382. if (input === H.doc.activeElement) { // Only when focused
  8383. // Update also when no `change` event is triggered, like when
  8384. // clicking inside the SVG (#4710)
  8385. updateExtremes();
  8386. }
  8387. // #10404 - move hide and blur outside focus
  8388. rangeSelector.hideInput(name);
  8389. input.blur(); // #4606
  8390. };
  8391. // handle changes in the input boxes
  8392. input.onchange = updateExtremes;
  8393. input.onkeypress = function (event) {
  8394. // IE does not fire onchange on enter
  8395. if (event.keyCode === 13) {
  8396. updateExtremes();
  8397. }
  8398. };
  8399. };
  8400. /**
  8401. * Get the position of the range selector buttons and inputs. This can be
  8402. * overridden from outside for custom positioning.
  8403. *
  8404. * @private
  8405. * @function Highcharts.RangeSelector#getPosition
  8406. *
  8407. * @return {Highcharts.Dictionary<number>}
  8408. */
  8409. RangeSelector.prototype.getPosition = function () {
  8410. var chart = this.chart,
  8411. options = chart.options.rangeSelector,
  8412. top = options.verticalAlign === 'top' ?
  8413. chart.plotTop - chart.axisOffset[0] :
  8414. 0; // set offset only for varticalAlign top
  8415. return {
  8416. buttonTop: top + options.buttonPosition.y,
  8417. inputTop: top + options.inputPosition.y - 10
  8418. };
  8419. };
  8420. /**
  8421. * Get the extremes of YTD. Will choose dataMax if its value is lower than
  8422. * the current timestamp. Will choose dataMin if its value is higher than
  8423. * the timestamp for the start of current year.
  8424. *
  8425. * @private
  8426. * @function Highcharts.RangeSelector#getYTDExtremes
  8427. *
  8428. * @param {number} dataMax
  8429. *
  8430. * @param {number} dataMin
  8431. *
  8432. * @return {*}
  8433. * Returns min and max for the YTD
  8434. */
  8435. RangeSelector.prototype.getYTDExtremes = function (dataMax, dataMin, useUTC) {
  8436. var time = this.chart.time,
  8437. min,
  8438. now = new time.Date(dataMax),
  8439. year = time.get('FullYear',
  8440. now),
  8441. startOfYear = useUTC ?
  8442. time.Date.UTC(year, 0, 1) : // eslint-disable-line new-cap
  8443. +new time.Date(year, 0, 1);
  8444. min = Math.max(dataMin || 0, startOfYear);
  8445. now = now.getTime();
  8446. return {
  8447. max: Math.min(dataMax || now, now),
  8448. min: min
  8449. };
  8450. };
  8451. /**
  8452. * Render the range selector including the buttons and the inputs. The first
  8453. * time render is called, the elements are created and positioned. On
  8454. * subsequent calls, they are moved and updated.
  8455. *
  8456. * @private
  8457. * @function Highcharts.RangeSelector#render
  8458. * @param {number} [min]
  8459. * X axis minimum
  8460. * @param {number} [max]
  8461. * X axis maximum
  8462. * @return {void}
  8463. */
  8464. RangeSelector.prototype.render = function (min, max) {
  8465. var rangeSelector = this,
  8466. chart = rangeSelector.chart,
  8467. renderer = chart.renderer,
  8468. container = chart.container,
  8469. chartOptions = chart.options,
  8470. navButtonOptions = (chartOptions.exporting &&
  8471. chartOptions.exporting.enabled !== false &&
  8472. chartOptions.navigation &&
  8473. chartOptions.navigation.buttonOptions),
  8474. lang = defaultOptions.lang,
  8475. div = rangeSelector.div,
  8476. options = chartOptions.rangeSelector,
  8477. // Place inputs above the container
  8478. inputsZIndex = pick(chartOptions.chart.style &&
  8479. chartOptions.chart.style.zIndex, 0) + 1,
  8480. floating = options.floating,
  8481. buttons = rangeSelector.buttons,
  8482. inputGroup = rangeSelector.inputGroup,
  8483. buttonTheme = options.buttonTheme,
  8484. buttonPosition = options.buttonPosition,
  8485. inputPosition = options.inputPosition,
  8486. inputEnabled = options.inputEnabled,
  8487. states = buttonTheme && buttonTheme.states,
  8488. plotLeft = chart.plotLeft,
  8489. buttonLeft,
  8490. buttonGroup = rangeSelector.buttonGroup,
  8491. group,
  8492. groupHeight,
  8493. rendered = rangeSelector.rendered,
  8494. verticalAlign = rangeSelector.options.verticalAlign,
  8495. legend = chart.legend,
  8496. legendOptions = legend && legend.options,
  8497. buttonPositionY = buttonPosition.y,
  8498. inputPositionY = inputPosition.y,
  8499. animate = chart.hasLoaded,
  8500. verb = animate ? 'animate' : 'attr',
  8501. exportingX = 0,
  8502. alignTranslateY,
  8503. legendHeight,
  8504. minPosition,
  8505. translateY = 0,
  8506. translateX;
  8507. if (options.enabled === false) {
  8508. return;
  8509. }
  8510. // create the elements
  8511. if (!rendered) {
  8512. rangeSelector.group = group = renderer.g('range-selector-group')
  8513. .attr({
  8514. zIndex: 7
  8515. })
  8516. .add();
  8517. rangeSelector.buttonGroup = buttonGroup =
  8518. renderer.g('range-selector-buttons').add(group);
  8519. rangeSelector.zoomText = renderer
  8520. .text(lang.rangeSelectorZoom, 0, 15)
  8521. .add(buttonGroup);
  8522. if (!chart.styledMode) {
  8523. rangeSelector.zoomText.css(options.labelStyle);
  8524. buttonTheme['stroke-width'] =
  8525. pick(buttonTheme['stroke-width'], 0);
  8526. }
  8527. rangeSelector.buttonOptions.forEach(function (rangeOptions, i) {
  8528. buttons[i] = renderer
  8529. .button(rangeOptions.text, 0, 0, function (e) {
  8530. // extract events from button object and call
  8531. var buttonEvents = (rangeOptions.events &&
  8532. rangeOptions.events.click),
  8533. callDefaultEvent;
  8534. if (buttonEvents) {
  8535. callDefaultEvent =
  8536. buttonEvents.call(rangeOptions, e);
  8537. }
  8538. if (callDefaultEvent !== false) {
  8539. rangeSelector.clickButton(i);
  8540. }
  8541. rangeSelector.isActive = true;
  8542. }, buttonTheme, states && states.hover, states && states.select, states && states.disabled)
  8543. .attr({
  8544. 'text-align': 'center'
  8545. })
  8546. .add(buttonGroup);
  8547. });
  8548. // first create a wrapper outside the container in order to make
  8549. // the inputs work and make export correct
  8550. if (inputEnabled !== false) {
  8551. rangeSelector.div = div = createElement('div', null, {
  8552. position: 'relative',
  8553. height: 0,
  8554. zIndex: inputsZIndex
  8555. });
  8556. container.parentNode.insertBefore(div, container);
  8557. // Create the group to keep the inputs
  8558. rangeSelector.inputGroup = inputGroup =
  8559. renderer.g('input-group').add(group);
  8560. inputGroup.offset = 0;
  8561. rangeSelector.drawInput('min');
  8562. rangeSelector.drawInput('max');
  8563. }
  8564. }
  8565. // #8769, allow dynamically updating margins
  8566. rangeSelector.zoomText[verb]({
  8567. x: pick(plotLeft + buttonPosition.x, plotLeft)
  8568. });
  8569. // button start position
  8570. buttonLeft = pick(plotLeft + buttonPosition.x, plotLeft) +
  8571. rangeSelector.zoomText.getBBox().width + 5;
  8572. rangeSelector.buttonOptions.forEach(function (rangeOptions, i) {
  8573. buttons[i][verb]({ x: buttonLeft });
  8574. // increase button position for the next button
  8575. buttonLeft += buttons[i].width + pick(options.buttonSpacing, 5);
  8576. });
  8577. plotLeft = chart.plotLeft - chart.spacing[3];
  8578. rangeSelector.updateButtonStates();
  8579. // detect collisiton with exporting
  8580. if (navButtonOptions &&
  8581. this.titleCollision(chart) &&
  8582. verticalAlign === 'top' &&
  8583. buttonPosition.align === 'right' && ((buttonPosition.y +
  8584. buttonGroup.getBBox().height - 12) <
  8585. ((navButtonOptions.y || 0) +
  8586. navButtonOptions.height))) {
  8587. exportingX = -40;
  8588. }
  8589. translateX = buttonPosition.x - chart.spacing[3];
  8590. if (buttonPosition.align === 'right') {
  8591. translateX += exportingX - plotLeft; // (#13014)
  8592. }
  8593. else if (buttonPosition.align === 'center') {
  8594. translateX -= plotLeft / 2;
  8595. }
  8596. // align button group
  8597. buttonGroup.align({
  8598. y: buttonPosition.y,
  8599. width: buttonGroup.getBBox().width,
  8600. align: buttonPosition.align,
  8601. x: translateX
  8602. }, true, chart.spacingBox);
  8603. // skip animation
  8604. rangeSelector.group.placed = animate;
  8605. rangeSelector.buttonGroup.placed = animate;
  8606. if (inputEnabled !== false) {
  8607. var inputGroupX,
  8608. inputGroupWidth,
  8609. buttonGroupX,
  8610. buttonGroupWidth;
  8611. // detect collision with exporting
  8612. if (navButtonOptions &&
  8613. this.titleCollision(chart) &&
  8614. verticalAlign === 'top' &&
  8615. inputPosition.align === 'right' && ((inputPosition.y -
  8616. inputGroup.getBBox().height - 12) <
  8617. ((navButtonOptions.y || 0) +
  8618. navButtonOptions.height +
  8619. chart.spacing[0]))) {
  8620. exportingX = -40;
  8621. }
  8622. else {
  8623. exportingX = 0;
  8624. }
  8625. if (inputPosition.align === 'left') {
  8626. translateX = plotLeft;
  8627. }
  8628. else if (inputPosition.align === 'right') {
  8629. translateX = -Math.max(chart.axisOffset[1], -exportingX);
  8630. }
  8631. // Update the alignment to the updated spacing box
  8632. inputGroup.align({
  8633. y: inputPosition.y,
  8634. width: inputGroup.getBBox().width,
  8635. align: inputPosition.align,
  8636. // fix wrong getBBox() value on right align
  8637. x: inputPosition.x + translateX - 2
  8638. }, true, chart.spacingBox);
  8639. // detect collision
  8640. inputGroupX = (inputGroup.alignAttr.translateX +
  8641. inputGroup.alignOptions.x -
  8642. exportingX +
  8643. // getBBox for detecing left margin
  8644. inputGroup.getBBox().x +
  8645. // 2px padding to not overlap input and label
  8646. 2);
  8647. inputGroupWidth = inputGroup.alignOptions.width;
  8648. buttonGroupX = buttonGroup.alignAttr.translateX +
  8649. buttonGroup.getBBox().x;
  8650. // 20 is minimal spacing between elements
  8651. buttonGroupWidth = buttonGroup.getBBox().width + 20;
  8652. if ((inputPosition.align ===
  8653. buttonPosition.align) || ((buttonGroupX + buttonGroupWidth > inputGroupX) &&
  8654. (inputGroupX + inputGroupWidth > buttonGroupX) &&
  8655. (buttonPositionY <
  8656. (inputPositionY +
  8657. inputGroup.getBBox().height)))) {
  8658. inputGroup.attr({
  8659. translateX: inputGroup.alignAttr.translateX +
  8660. (chart.axisOffset[1] >= -exportingX ? 0 : -exportingX),
  8661. translateY: inputGroup.alignAttr.translateY +
  8662. buttonGroup.getBBox().height + 10
  8663. });
  8664. }
  8665. // Set or reset the input values
  8666. rangeSelector.setInputValue('min', min);
  8667. rangeSelector.setInputValue('max', max);
  8668. // skip animation
  8669. rangeSelector.inputGroup.placed = animate;
  8670. }
  8671. // vertical align
  8672. rangeSelector.group.align({
  8673. verticalAlign: verticalAlign
  8674. }, true, chart.spacingBox);
  8675. // set position
  8676. groupHeight =
  8677. rangeSelector.group.getBBox().height + 20; // # 20 padding
  8678. alignTranslateY =
  8679. rangeSelector.group.alignAttr.translateY;
  8680. // calculate bottom position
  8681. if (verticalAlign === 'bottom') {
  8682. legendHeight = (legendOptions &&
  8683. legendOptions.verticalAlign === 'bottom' &&
  8684. legendOptions.enabled &&
  8685. !legendOptions.floating ?
  8686. legend.legendHeight + pick(legendOptions.margin, 10) :
  8687. 0);
  8688. groupHeight = groupHeight + legendHeight - 20;
  8689. translateY = (alignTranslateY -
  8690. groupHeight -
  8691. (floating ? 0 : options.y) -
  8692. (chart.titleOffset ? chart.titleOffset[2] : 0) -
  8693. 10 // 10 spacing
  8694. );
  8695. }
  8696. if (verticalAlign === 'top') {
  8697. if (floating) {
  8698. translateY = 0;
  8699. }
  8700. if (chart.titleOffset && chart.titleOffset[0]) {
  8701. translateY = chart.titleOffset[0];
  8702. }
  8703. translateY += ((chart.margin[0] - chart.spacing[0]) || 0);
  8704. }
  8705. else if (verticalAlign === 'middle') {
  8706. if (inputPositionY === buttonPositionY) {
  8707. if (inputPositionY < 0) {
  8708. translateY = alignTranslateY + minPosition;
  8709. }
  8710. else {
  8711. translateY = alignTranslateY;
  8712. }
  8713. }
  8714. else if (inputPositionY || buttonPositionY) {
  8715. if (inputPositionY < 0 ||
  8716. buttonPositionY < 0) {
  8717. translateY -= Math.min(inputPositionY, buttonPositionY);
  8718. }
  8719. else {
  8720. translateY =
  8721. alignTranslateY - groupHeight + minPosition;
  8722. }
  8723. }
  8724. }
  8725. rangeSelector.group.translate(options.x, options.y + Math.floor(translateY));
  8726. // translate HTML inputs
  8727. if (inputEnabled !== false) {
  8728. rangeSelector.minInput.style.marginTop =
  8729. rangeSelector.group.translateY + 'px';
  8730. rangeSelector.maxInput.style.marginTop =
  8731. rangeSelector.group.translateY + 'px';
  8732. }
  8733. rangeSelector.rendered = true;
  8734. };
  8735. /**
  8736. * Extracts height of range selector
  8737. *
  8738. * @private
  8739. * @function Highcharts.RangeSelector#getHeight
  8740. * @return {number}
  8741. * Returns rangeSelector height
  8742. */
  8743. RangeSelector.prototype.getHeight = function () {
  8744. var rangeSelector = this,
  8745. options = rangeSelector.options,
  8746. rangeSelectorGroup = rangeSelector.group,
  8747. inputPosition = options.inputPosition,
  8748. buttonPosition = options.buttonPosition,
  8749. yPosition = options.y,
  8750. buttonPositionY = buttonPosition.y,
  8751. inputPositionY = inputPosition.y,
  8752. rangeSelectorHeight = 0,
  8753. minPosition;
  8754. if (options.height) {
  8755. return options.height;
  8756. }
  8757. rangeSelectorHeight = rangeSelectorGroup ?
  8758. // 13px to keep back compatibility
  8759. (rangeSelectorGroup.getBBox(true).height) + 13 +
  8760. yPosition :
  8761. 0;
  8762. minPosition = Math.min(inputPositionY, buttonPositionY);
  8763. if ((inputPositionY < 0 && buttonPositionY < 0) ||
  8764. (inputPositionY > 0 && buttonPositionY > 0)) {
  8765. rangeSelectorHeight += Math.abs(minPosition);
  8766. }
  8767. return rangeSelectorHeight;
  8768. };
  8769. /**
  8770. * Detect collision with title or subtitle
  8771. *
  8772. * @private
  8773. * @function Highcharts.RangeSelector#titleCollision
  8774. *
  8775. * @param {Highcharts.Chart} chart
  8776. *
  8777. * @return {boolean}
  8778. * Returns collision status
  8779. */
  8780. RangeSelector.prototype.titleCollision = function (chart) {
  8781. return !(chart.options.title.text ||
  8782. chart.options.subtitle.text);
  8783. };
  8784. /**
  8785. * Update the range selector with new options
  8786. *
  8787. * @private
  8788. * @function Highcharts.RangeSelector#update
  8789. * @param {Highcharts.RangeSelectorOptions} options
  8790. * @return {void}
  8791. */
  8792. RangeSelector.prototype.update = function (options) {
  8793. var chart = this.chart;
  8794. merge(true, chart.options.rangeSelector, options);
  8795. this.destroy();
  8796. this.init(chart);
  8797. chart.rangeSelector.render();
  8798. };
  8799. /**
  8800. * Destroys allocated elements.
  8801. *
  8802. * @private
  8803. * @function Highcharts.RangeSelector#destroy
  8804. */
  8805. RangeSelector.prototype.destroy = function () {
  8806. var rSelector = this,
  8807. minInput = rSelector.minInput,
  8808. maxInput = rSelector.maxInput;
  8809. rSelector.unMouseDown();
  8810. rSelector.unResize();
  8811. // Destroy elements in collections
  8812. destroyObjectProperties(rSelector.buttons);
  8813. // Clear input element events
  8814. if (minInput) {
  8815. minInput.onfocus = minInput.onblur = minInput.onchange = null;
  8816. }
  8817. if (maxInput) {
  8818. maxInput.onfocus = maxInput.onblur = maxInput.onchange = null;
  8819. }
  8820. // Destroy HTML and SVG elements
  8821. objectEach(rSelector, function (val, key) {
  8822. if (val && key !== 'chart') {
  8823. if (val instanceof SVGElement) {
  8824. // SVGElement
  8825. val.destroy();
  8826. }
  8827. else if (val instanceof window.HTMLElement) {
  8828. // HTML element
  8829. discardElement(val);
  8830. }
  8831. }
  8832. if (val !== RangeSelector.prototype[key]) {
  8833. rSelector[key] = null;
  8834. }
  8835. }, this);
  8836. };
  8837. return RangeSelector;
  8838. }());
  8839. /**
  8840. * The default buttons for pre-selecting time frames
  8841. */
  8842. RangeSelector.prototype.defaultButtons = [{
  8843. type: 'month',
  8844. count: 1,
  8845. text: '1m'
  8846. }, {
  8847. type: 'month',
  8848. count: 3,
  8849. text: '3m'
  8850. }, {
  8851. type: 'month',
  8852. count: 6,
  8853. text: '6m'
  8854. }, {
  8855. type: 'ytd',
  8856. text: 'YTD'
  8857. }, {
  8858. type: 'year',
  8859. count: 1,
  8860. text: '1y'
  8861. }, {
  8862. type: 'all',
  8863. text: 'All'
  8864. }];
  8865. /**
  8866. * Get the axis min value based on the range option and the current max. For
  8867. * stock charts this is extended via the {@link RangeSelector} so that if the
  8868. * selected range is a multiple of months or years, it is compensated for
  8869. * various month lengths.
  8870. *
  8871. * @private
  8872. * @function Highcharts.Axis#minFromRange
  8873. * @return {number|undefined}
  8874. * The new minimum value.
  8875. */
  8876. Axis.prototype.minFromRange = function () {
  8877. var rangeOptions = this.range,
  8878. type = rangeOptions.type,
  8879. min,
  8880. max = this.max,
  8881. dataMin,
  8882. range,
  8883. time = this.chart.time,
  8884. // Get the true range from a start date
  8885. getTrueRange = function (base,
  8886. count) {
  8887. var timeName = type === 'year' ? 'FullYear' : 'Month';
  8888. var date = new time.Date(base);
  8889. var basePeriod = time.get(timeName,
  8890. date);
  8891. time.set(timeName, date, basePeriod + count);
  8892. if (basePeriod === time.get(timeName, date)) {
  8893. time.set('Date', date, 0); // #6537
  8894. }
  8895. return date.getTime() - base;
  8896. };
  8897. if (isNumber(rangeOptions)) {
  8898. min = max - rangeOptions;
  8899. range = rangeOptions;
  8900. }
  8901. else {
  8902. min = max + getTrueRange(max, -rangeOptions.count);
  8903. // Let the fixedRange reflect initial settings (#5930)
  8904. if (this.chart) {
  8905. this.chart.fixedRange = max - min;
  8906. }
  8907. }
  8908. dataMin = pick(this.dataMin, Number.MIN_VALUE);
  8909. if (!isNumber(min)) {
  8910. min = dataMin;
  8911. }
  8912. if (min <= dataMin) {
  8913. min = dataMin;
  8914. if (typeof range === 'undefined') { // #4501
  8915. range = getTrueRange(min, rangeOptions.count);
  8916. }
  8917. this.newMax = Math.min(min + range, this.dataMax);
  8918. }
  8919. if (!isNumber(max)) {
  8920. min = void 0;
  8921. }
  8922. return min;
  8923. };
  8924. if (!H.RangeSelector) {
  8925. // Initialize rangeselector for stock charts
  8926. addEvent(Chart, 'afterGetContainer', function () {
  8927. if (this.options.rangeSelector.enabled) {
  8928. this.rangeSelector = new RangeSelector(this);
  8929. }
  8930. });
  8931. addEvent(Chart, 'beforeRender', function () {
  8932. var chart = this,
  8933. axes = chart.axes,
  8934. rangeSelector = chart.rangeSelector,
  8935. verticalAlign;
  8936. if (rangeSelector) {
  8937. if (isNumber(rangeSelector.deferredYTDClick)) {
  8938. rangeSelector.clickButton(rangeSelector.deferredYTDClick);
  8939. delete rangeSelector.deferredYTDClick;
  8940. }
  8941. axes.forEach(function (axis) {
  8942. axis.updateNames();
  8943. axis.setScale();
  8944. });
  8945. chart.getAxisMargins();
  8946. rangeSelector.render();
  8947. verticalAlign = rangeSelector.options.verticalAlign;
  8948. if (!rangeSelector.options.floating) {
  8949. if (verticalAlign === 'bottom') {
  8950. this.extraBottomMargin = true;
  8951. }
  8952. else if (verticalAlign !== 'middle') {
  8953. this.extraTopMargin = true;
  8954. }
  8955. }
  8956. }
  8957. });
  8958. addEvent(Chart, 'update', function (e) {
  8959. var chart = this,
  8960. options = e.options,
  8961. optionsRangeSelector = options.rangeSelector,
  8962. rangeSelector = chart.rangeSelector,
  8963. verticalAlign,
  8964. extraBottomMarginWas = this.extraBottomMargin,
  8965. extraTopMarginWas = this.extraTopMargin;
  8966. if (optionsRangeSelector &&
  8967. optionsRangeSelector.enabled &&
  8968. !defined(rangeSelector)) {
  8969. this.options.rangeSelector.enabled = true;
  8970. this.rangeSelector = new RangeSelector(this);
  8971. }
  8972. this.extraBottomMargin = false;
  8973. this.extraTopMargin = false;
  8974. if (rangeSelector) {
  8975. rangeSelector.render();
  8976. verticalAlign = (optionsRangeSelector &&
  8977. optionsRangeSelector.verticalAlign) || (rangeSelector.options && rangeSelector.options.verticalAlign);
  8978. if (!rangeSelector.options.floating) {
  8979. if (verticalAlign === 'bottom') {
  8980. this.extraBottomMargin = true;
  8981. }
  8982. else if (verticalAlign !== 'middle') {
  8983. this.extraTopMargin = true;
  8984. }
  8985. }
  8986. if (this.extraBottomMargin !== extraBottomMarginWas ||
  8987. this.extraTopMargin !== extraTopMarginWas) {
  8988. this.isDirtyBox = true;
  8989. }
  8990. }
  8991. });
  8992. addEvent(Chart, 'render', function () {
  8993. var chart = this,
  8994. rangeSelector = chart.rangeSelector,
  8995. verticalAlign;
  8996. if (rangeSelector && !rangeSelector.options.floating) {
  8997. rangeSelector.render();
  8998. verticalAlign = rangeSelector.options.verticalAlign;
  8999. if (verticalAlign === 'bottom') {
  9000. this.extraBottomMargin = true;
  9001. }
  9002. else if (verticalAlign !== 'middle') {
  9003. this.extraTopMargin = true;
  9004. }
  9005. }
  9006. });
  9007. addEvent(Chart, 'getMargins', function () {
  9008. var rangeSelector = this.rangeSelector,
  9009. rangeSelectorHeight;
  9010. if (rangeSelector) {
  9011. rangeSelectorHeight = rangeSelector.getHeight();
  9012. if (this.extraTopMargin) {
  9013. this.plotTop += rangeSelectorHeight;
  9014. }
  9015. if (this.extraBottomMargin) {
  9016. this.marginBottom += rangeSelectorHeight;
  9017. }
  9018. }
  9019. });
  9020. Chart.prototype.callbacks.push(function (chart) {
  9021. var extremes,
  9022. rangeSelector = chart.rangeSelector,
  9023. unbindRender,
  9024. unbindSetExtremes,
  9025. legend,
  9026. alignTo,
  9027. verticalAlign;
  9028. /**
  9029. * @private
  9030. */
  9031. function renderRangeSelector() {
  9032. extremes = chart.xAxis[0].getExtremes();
  9033. legend = chart.legend;
  9034. verticalAlign = rangeSelector === null || rangeSelector === void 0 ? void 0 : rangeSelector.options.verticalAlign;
  9035. if (isNumber(extremes.min)) {
  9036. rangeSelector.render(extremes.min, extremes.max);
  9037. }
  9038. // Re-align the legend so that it's below the rangeselector
  9039. if (rangeSelector && legend.display &&
  9040. verticalAlign === 'top' &&
  9041. verticalAlign === legend.options.verticalAlign) {
  9042. // Create a new alignment box for the legend.
  9043. alignTo = merge(chart.spacingBox);
  9044. if (legend.options.layout === 'vertical') {
  9045. alignTo.y = chart.plotTop;
  9046. }
  9047. else {
  9048. alignTo.y += rangeSelector.getHeight();
  9049. }
  9050. legend.group.placed = false; // Don't animate the alignment.
  9051. legend.align(alignTo);
  9052. }
  9053. }
  9054. if (rangeSelector) {
  9055. // redraw the scroller on setExtremes
  9056. unbindSetExtremes = addEvent(chart.xAxis[0], 'afterSetExtremes', function (e) {
  9057. rangeSelector.render(e.min, e.max);
  9058. });
  9059. // redraw the scroller chart resize
  9060. unbindRender = addEvent(chart, 'redraw', renderRangeSelector);
  9061. // do it now
  9062. renderRangeSelector();
  9063. }
  9064. // Remove resize/afterSetExtremes at chart destroy
  9065. addEvent(chart, 'destroy', function destroyEvents() {
  9066. if (rangeSelector) {
  9067. unbindRender();
  9068. unbindSetExtremes();
  9069. }
  9070. });
  9071. });
  9072. H.RangeSelector = RangeSelector;
  9073. }
  9074. return H.RangeSelector;
  9075. });
  9076. _registerModule(_modules, 'Core/Chart/StockChart.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/Series/Point.js'], _modules['Core/Renderer/SVG/SVGRenderer.js'], _modules['Core/Utilities.js']], function (Axis, Chart, H, Point, SVGRenderer, U) {
  9077. /* *
  9078. *
  9079. * (c) 2010-2020 Torstein Honsi
  9080. *
  9081. * License: www.highcharts.com/license
  9082. *
  9083. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  9084. *
  9085. * */
  9086. var addEvent = U.addEvent,
  9087. arrayMax = U.arrayMax,
  9088. arrayMin = U.arrayMin,
  9089. clamp = U.clamp,
  9090. defined = U.defined,
  9091. extend = U.extend,
  9092. find = U.find,
  9093. format = U.format,
  9094. getOptions = U.getOptions,
  9095. isNumber = U.isNumber,
  9096. isString = U.isString,
  9097. merge = U.merge,
  9098. pick = U.pick,
  9099. splat = U.splat;
  9100. // Has a dependency on Navigator due to the use of
  9101. // defaultOptions.navigator
  9102. // Has a dependency on Scrollbar due to the use of
  9103. // defaultOptions.scrollbar
  9104. // Has a dependency on RangeSelector due to the use of
  9105. // defaultOptions.rangeSelector
  9106. var Series = H.Series,
  9107. seriesProto = Series.prototype,
  9108. seriesInit = seriesProto.init,
  9109. seriesProcessData = seriesProto.processData,
  9110. pointTooltipFormatter = Point.prototype.tooltipFormatter;
  9111. /**
  9112. * Compare the values of the series against the first non-null, non-
  9113. * zero value in the visible range. The y axis will show percentage
  9114. * or absolute change depending on whether `compare` is set to `"percent"`
  9115. * or `"value"`. When this is applied to multiple series, it allows
  9116. * comparing the development of the series against each other. Adds
  9117. * a `change` field to every point object.
  9118. *
  9119. * @see [compareBase](#plotOptions.series.compareBase)
  9120. * @see [Axis.setCompare()](/class-reference/Highcharts.Axis#setCompare)
  9121. *
  9122. * @sample {highstock} stock/plotoptions/series-compare-percent/
  9123. * Percent
  9124. * @sample {highstock} stock/plotoptions/series-compare-value/
  9125. * Value
  9126. *
  9127. * @type {string}
  9128. * @since 1.0.1
  9129. * @product highstock
  9130. * @apioption plotOptions.series.compare
  9131. */
  9132. /**
  9133. * Defines if comparison should start from the first point within the visible
  9134. * range or should start from the first point **before** the range.
  9135. *
  9136. * In other words, this flag determines if first point within the visible range
  9137. * will have 0% (`compareStart=true`) or should have been already calculated
  9138. * according to the previous point (`compareStart=false`).
  9139. *
  9140. * @sample {highstock} stock/plotoptions/series-comparestart/
  9141. * Calculate compare within visible range
  9142. *
  9143. * @type {boolean}
  9144. * @default false
  9145. * @since 6.0.0
  9146. * @product highstock
  9147. * @apioption plotOptions.series.compareStart
  9148. */
  9149. /**
  9150. * When [compare](#plotOptions.series.compare) is `percent`, this option
  9151. * dictates whether to use 0 or 100 as the base of comparison.
  9152. *
  9153. * @sample {highstock} stock/plotoptions/series-comparebase/
  9154. * Compare base is 100
  9155. *
  9156. * @type {number}
  9157. * @default 0
  9158. * @since 5.0.6
  9159. * @product highstock
  9160. * @validvalue [0, 100]
  9161. * @apioption plotOptions.series.compareBase
  9162. */
  9163. /* eslint-disable no-invalid-this, valid-jsdoc */
  9164. /**
  9165. * Factory function for creating new stock charts. Creates a new
  9166. * {@link Highcharts.Chart|Chart} object with different default options than the
  9167. * basic Chart.
  9168. *
  9169. * @example
  9170. * var chart = Highcharts.stockChart('container', {
  9171. * series: [{
  9172. * data: [1, 2, 3, 4, 5, 6, 7, 8, 9],
  9173. * pointInterval: 24 * 60 * 60 * 1000
  9174. * }]
  9175. * });
  9176. *
  9177. * @function Highcharts.stockChart
  9178. *
  9179. * @param {string|Highcharts.HTMLDOMElement} [renderTo]
  9180. * The DOM element to render to, or its id.
  9181. *
  9182. * @param {Highcharts.Options} options
  9183. * The chart options structure as described in the
  9184. * [options reference](https://api.highcharts.com/highstock).
  9185. *
  9186. * @param {Highcharts.ChartCallbackFunction} [callback]
  9187. * A function to execute when the chart object is finished loading and
  9188. * rendering. In most cases the chart is built in one thread, but in
  9189. * Internet Explorer version 8 or less the chart is sometimes
  9190. * initialized before the document is ready, and in these cases the
  9191. * chart object will not be finished synchronously. As a consequence,
  9192. * code that relies on the newly built Chart object should always run in
  9193. * the callback. Defining a
  9194. * [chart.events.load](https://api.highcharts.com/highstock/chart.events.load)
  9195. * handler is equivalent.
  9196. *
  9197. * @return {Highcharts.Chart}
  9198. * The chart object.
  9199. */
  9200. H.StockChart = H.stockChart = function (a, b, c) {
  9201. var hasRenderToArg = isString(a) || a.nodeName,
  9202. options = arguments[hasRenderToArg ? 1 : 0],
  9203. userOptions = options,
  9204. // to increase performance, don't merge the data
  9205. seriesOptions = options.series,
  9206. defaultOptions = getOptions(),
  9207. opposite,
  9208. // Always disable startOnTick:true on the main axis when the navigator
  9209. // is enabled (#1090)
  9210. navigatorEnabled = pick(options.navigator && options.navigator.enabled,
  9211. defaultOptions.navigator.enabled,
  9212. true);
  9213. // apply X axis options to both single and multi y axes
  9214. options.xAxis = splat(options.xAxis || {}).map(function (xAxisOptions, i) {
  9215. return merge({
  9216. minPadding: 0,
  9217. maxPadding: 0,
  9218. overscroll: 0,
  9219. ordinal: true,
  9220. title: {
  9221. text: null
  9222. },
  9223. labels: {
  9224. overflow: 'justify'
  9225. },
  9226. showLastLabel: true
  9227. }, defaultOptions.xAxis, // #3802
  9228. defaultOptions.xAxis && defaultOptions.xAxis[i], // #7690
  9229. xAxisOptions, // user options
  9230. {
  9231. type: 'datetime',
  9232. categories: null
  9233. }, (navigatorEnabled ? {
  9234. startOnTick: false,
  9235. endOnTick: false
  9236. } : null));
  9237. });
  9238. // apply Y axis options to both single and multi y axes
  9239. options.yAxis = splat(options.yAxis || {}).map(function (yAxisOptions, i) {
  9240. opposite = pick(yAxisOptions.opposite, true);
  9241. return merge({
  9242. labels: {
  9243. y: -2
  9244. },
  9245. opposite: opposite,
  9246. /**
  9247. * @default {highcharts} true
  9248. * @default {highstock} false
  9249. * @apioption yAxis.showLastLabel
  9250. *
  9251. * @private
  9252. */
  9253. showLastLabel: !!(
  9254. // #6104, show last label by default for category axes
  9255. yAxisOptions.categories ||
  9256. yAxisOptions.type === 'category'),
  9257. title: {
  9258. text: null
  9259. }
  9260. }, defaultOptions.yAxis, // #3802
  9261. defaultOptions.yAxis && defaultOptions.yAxis[i], // #7690
  9262. yAxisOptions // user options
  9263. );
  9264. });
  9265. options.series = null;
  9266. options = merge({
  9267. chart: {
  9268. panning: {
  9269. enabled: true,
  9270. type: 'x'
  9271. },
  9272. pinchType: 'x'
  9273. },
  9274. navigator: {
  9275. enabled: navigatorEnabled
  9276. },
  9277. scrollbar: {
  9278. // #4988 - check if setOptions was called
  9279. enabled: pick(defaultOptions.scrollbar.enabled, true)
  9280. },
  9281. rangeSelector: {
  9282. // #4988 - check if setOptions was called
  9283. enabled: pick(defaultOptions.rangeSelector.enabled, true)
  9284. },
  9285. title: {
  9286. text: null
  9287. },
  9288. tooltip: {
  9289. split: pick(defaultOptions.tooltip.split, true),
  9290. crosshairs: true
  9291. },
  9292. legend: {
  9293. enabled: false
  9294. }
  9295. }, options, // user's options
  9296. {
  9297. isStock: true // internal flag
  9298. });
  9299. options.series = userOptions.series = seriesOptions;
  9300. return hasRenderToArg ?
  9301. new Chart(a, options, c) :
  9302. new Chart(options, b);
  9303. };
  9304. // Handle som Stock-specific series defaults, override the plotOptions before
  9305. // series options are handled.
  9306. addEvent(Series, 'setOptions', function (e) {
  9307. var overrides;
  9308. if (this.chart.options.isStock) {
  9309. if (this.is('column') || this.is('columnrange')) {
  9310. overrides = {
  9311. borderWidth: 0,
  9312. shadow: false
  9313. };
  9314. }
  9315. else if (!this.is('scatter') && !this.is('sma')) {
  9316. overrides = {
  9317. marker: {
  9318. enabled: false,
  9319. radius: 2
  9320. }
  9321. };
  9322. }
  9323. if (overrides) {
  9324. e.plotOptions[this.type] = merge(e.plotOptions[this.type], overrides);
  9325. }
  9326. }
  9327. });
  9328. // Override the automatic label alignment so that the first Y axis' labels
  9329. // are drawn on top of the grid line, and subsequent axes are drawn outside
  9330. addEvent(Axis, 'autoLabelAlign', function (e) {
  9331. var chart = this.chart,
  9332. options = this.options,
  9333. panes = chart._labelPanes = chart._labelPanes || {},
  9334. key,
  9335. labelOptions = this.options.labels;
  9336. if (this.chart.options.isStock && this.coll === 'yAxis') {
  9337. key = options.top + ',' + options.height;
  9338. // do it only for the first Y axis of each pane
  9339. if (!panes[key] && labelOptions.enabled) {
  9340. if (labelOptions.x === 15) { // default
  9341. labelOptions.x = 0;
  9342. }
  9343. if (typeof labelOptions.align === 'undefined') {
  9344. labelOptions.align = 'right';
  9345. }
  9346. panes[key] = this;
  9347. e.align = 'right';
  9348. e.preventDefault();
  9349. }
  9350. }
  9351. });
  9352. // Clear axis from label panes (#6071)
  9353. addEvent(Axis, 'destroy', function () {
  9354. var chart = this.chart, key = this.options && (this.options.top + ',' + this.options.height);
  9355. if (key && chart._labelPanes && chart._labelPanes[key] === this) {
  9356. delete chart._labelPanes[key];
  9357. }
  9358. });
  9359. // Override getPlotLinePath to allow for multipane charts
  9360. addEvent(Axis, 'getPlotLinePath', function (e) {
  9361. var axis = this,
  9362. series = (this.isLinked && !this.series ?
  9363. this.linkedParent.series :
  9364. this.series),
  9365. chart = axis.chart,
  9366. renderer = chart.renderer,
  9367. axisLeft = axis.left,
  9368. axisTop = axis.top,
  9369. x1,
  9370. y1,
  9371. x2,
  9372. y2,
  9373. result = [],
  9374. axes = [], // #3416 need a default array
  9375. axes2,
  9376. uniqueAxes,
  9377. translatedValue = e.translatedValue,
  9378. value = e.value,
  9379. force = e.force,
  9380. transVal;
  9381. /**
  9382. * Return the other axis based on either the axis option or on related
  9383. * series.
  9384. * @private
  9385. */
  9386. function getAxis(coll) {
  9387. var otherColl = coll === 'xAxis' ? 'yAxis' : 'xAxis',
  9388. opt = axis.options[otherColl];
  9389. // Other axis indexed by number
  9390. if (isNumber(opt)) {
  9391. return [chart[otherColl][opt]];
  9392. }
  9393. // Other axis indexed by id (like navigator)
  9394. if (isString(opt)) {
  9395. return [chart.get(opt)];
  9396. }
  9397. // Auto detect based on existing series
  9398. return series.map(function (s) {
  9399. return s[otherColl];
  9400. });
  9401. }
  9402. if ( // For stock chart, by default render paths across the panes
  9403. // except the case when `acrossPanes` is disabled by user (#6644)
  9404. (chart.options.isStock && e.acrossPanes !== false) &&
  9405. // Ignore in case of colorAxis or zAxis. #3360, #3524, #6720
  9406. axis.coll === 'xAxis' || axis.coll === 'yAxis') {
  9407. e.preventDefault();
  9408. // Get the related axes based on series
  9409. axes = getAxis(axis.coll);
  9410. // Get the related axes based options.*Axis setting #2810
  9411. axes2 = (axis.isXAxis ? chart.yAxis : chart.xAxis);
  9412. axes2.forEach(function (A) {
  9413. if (defined(A.options.id) ?
  9414. A.options.id.indexOf('navigator') === -1 :
  9415. true) {
  9416. var a = (A.isXAxis ? 'yAxis' : 'xAxis'),
  9417. rax = (defined(A.options[a]) ?
  9418. chart[a][A.options[a]] :
  9419. chart[a][0]);
  9420. if (axis === rax) {
  9421. axes.push(A);
  9422. }
  9423. }
  9424. });
  9425. // Remove duplicates in the axes array. If there are no axes in the axes
  9426. // array, we are adding an axis without data, so we need to populate
  9427. // this with grid lines (#2796).
  9428. uniqueAxes = axes.length ?
  9429. [] :
  9430. [axis.isXAxis ? chart.yAxis[0] : chart.xAxis[0]]; // #3742
  9431. axes.forEach(function (axis2) {
  9432. if (uniqueAxes.indexOf(axis2) === -1 &&
  9433. // Do not draw on axis which overlap completely. #5424
  9434. !find(uniqueAxes, function (unique) {
  9435. return unique.pos === axis2.pos && unique.len === axis2.len;
  9436. })) {
  9437. uniqueAxes.push(axis2);
  9438. }
  9439. });
  9440. transVal = pick(translatedValue, axis.translate(value, null, null, e.old));
  9441. if (isNumber(transVal)) {
  9442. if (axis.horiz) {
  9443. uniqueAxes.forEach(function (axis2) {
  9444. var skip;
  9445. y1 = axis2.pos;
  9446. y2 = y1 + axis2.len;
  9447. x1 = x2 = Math.round(transVal + axis.transB);
  9448. // outside plot area
  9449. if (force !== 'pass' &&
  9450. (x1 < axisLeft || x1 > axisLeft + axis.width)) {
  9451. if (force) {
  9452. x1 = x2 = clamp(x1, axisLeft, axisLeft + axis.width);
  9453. }
  9454. else {
  9455. skip = true;
  9456. }
  9457. }
  9458. if (!skip) {
  9459. result.push(['M', x1, y1], ['L', x2, y2]);
  9460. }
  9461. });
  9462. }
  9463. else {
  9464. uniqueAxes.forEach(function (axis2) {
  9465. var skip;
  9466. x1 = axis2.pos;
  9467. x2 = x1 + axis2.len;
  9468. y1 = y2 = Math.round(axisTop + axis.height - transVal);
  9469. // outside plot area
  9470. if (force !== 'pass' &&
  9471. (y1 < axisTop || y1 > axisTop + axis.height)) {
  9472. if (force) {
  9473. y1 = y2 = clamp(y1, axisTop, axisTop + axis.height);
  9474. }
  9475. else {
  9476. skip = true;
  9477. }
  9478. }
  9479. if (!skip) {
  9480. result.push(['M', x1, y1], ['L', x2, y2]);
  9481. }
  9482. });
  9483. }
  9484. }
  9485. e.path = result.length > 0 ?
  9486. renderer.crispPolyLine(result, e.lineWidth || 1) :
  9487. // #3557 getPlotLinePath in regular Highcharts also returns null
  9488. null;
  9489. }
  9490. });
  9491. /**
  9492. * Function to crisp a line with multiple segments
  9493. *
  9494. * @private
  9495. * @function Highcharts.SVGRenderer#crispPolyLine
  9496. * @param {Highcharts.SVGPathArray} points
  9497. * @param {number} width
  9498. * @return {Highcharts.SVGPathArray}
  9499. */
  9500. SVGRenderer.prototype.crispPolyLine = function (points, width) {
  9501. // points format: [['M', 0, 0], ['L', 100, 0]]
  9502. // normalize to a crisp line
  9503. for (var i = 0; i < points.length; i = i + 2) {
  9504. var start = points[i],
  9505. end = points[i + 1];
  9506. if (start[1] === end[1]) {
  9507. // Substract due to #1129. Now bottom and left axis gridlines behave
  9508. // the same.
  9509. start[1] = end[1] =
  9510. Math.round(start[1]) - (width % 2 / 2);
  9511. }
  9512. if (start[2] === end[2]) {
  9513. start[2] = end[2] =
  9514. Math.round(start[2]) + (width % 2 / 2);
  9515. }
  9516. }
  9517. return points;
  9518. };
  9519. // Wrapper to hide the label
  9520. addEvent(Axis, 'afterHideCrosshair', function () {
  9521. if (this.crossLabel) {
  9522. this.crossLabel = this.crossLabel.hide();
  9523. }
  9524. });
  9525. // Extend crosshairs to also draw the label
  9526. addEvent(Axis, 'afterDrawCrosshair', function (event) {
  9527. // Check if the label has to be drawn
  9528. if (!defined(this.crosshair.label) ||
  9529. !this.crosshair.label.enabled ||
  9530. !this.cross) {
  9531. return;
  9532. }
  9533. var chart = this.chart, log = this.logarithmic, options = this.options.crosshair.label, // the label's options
  9534. horiz = this.horiz, // axis orientation
  9535. opposite = this.opposite, // axis position
  9536. left = this.left, // left position
  9537. top = this.top, // top position
  9538. crossLabel = this.crossLabel, // the svgElement
  9539. posx, posy, crossBox, formatOption = options.format, formatFormat = '', limit, align, tickInside = this.options.tickPosition === 'inside', snap = this.crosshair.snap !== false, value, offset = 0,
  9540. // Use last available event (#5287)
  9541. e = event.e || (this.cross && this.cross.e), point = event.point, min = this.min, max = this.max;
  9542. if (log) {
  9543. min = log.lin2log(min);
  9544. max = log.lin2log(max);
  9545. }
  9546. align = (horiz ? 'center' : opposite ?
  9547. (this.labelAlign === 'right' ? 'right' : 'left') :
  9548. (this.labelAlign === 'left' ? 'left' : 'center'));
  9549. // If the label does not exist yet, create it.
  9550. if (!crossLabel) {
  9551. crossLabel = this.crossLabel = chart.renderer
  9552. .label(null, null, null, options.shape || 'callout')
  9553. .addClass('highcharts-crosshair-label' + (this.series[0] &&
  9554. ' highcharts-color-' + this.series[0].colorIndex))
  9555. .attr({
  9556. align: options.align || align,
  9557. padding: pick(options.padding, 8),
  9558. r: pick(options.borderRadius, 3),
  9559. zIndex: 2
  9560. })
  9561. .add(this.labelGroup);
  9562. // Presentational
  9563. if (!chart.styledMode) {
  9564. crossLabel
  9565. .attr({
  9566. fill: options.backgroundColor ||
  9567. (this.series[0] && this.series[0].color) ||
  9568. '#666666',
  9569. stroke: options.borderColor || '',
  9570. 'stroke-width': options.borderWidth || 0
  9571. })
  9572. .css(extend({
  9573. color: '#ffffff',
  9574. fontWeight: 'normal',
  9575. fontSize: '11px',
  9576. textAlign: 'center'
  9577. }, options.style));
  9578. }
  9579. }
  9580. if (horiz) {
  9581. posx = snap ? point.plotX + left : e.chartX;
  9582. posy = top + (opposite ? 0 : this.height);
  9583. }
  9584. else {
  9585. posx = opposite ? this.width + left : 0;
  9586. posy = snap ? point.plotY + top : e.chartY;
  9587. }
  9588. if (!formatOption && !options.formatter) {
  9589. if (this.dateTime) {
  9590. formatFormat = '%b %d, %Y';
  9591. }
  9592. formatOption =
  9593. '{value' + (formatFormat ? ':' + formatFormat : '') + '}';
  9594. }
  9595. // Show the label
  9596. value = snap ?
  9597. point[this.isXAxis ? 'x' : 'y'] :
  9598. this.toValue(horiz ? e.chartX : e.chartY);
  9599. crossLabel.attr({
  9600. text: formatOption ?
  9601. format(formatOption, { value: value }, chart) :
  9602. options.formatter.call(this, value),
  9603. x: posx,
  9604. y: posy,
  9605. // Crosshair should be rendered within Axis range (#7219)
  9606. visibility: value < min || value > max ?
  9607. 'hidden' :
  9608. 'visible'
  9609. });
  9610. crossBox = crossLabel.getBBox();
  9611. // now it is placed we can correct its position
  9612. if (isNumber(crossLabel.y)) {
  9613. if (horiz) {
  9614. if ((tickInside && !opposite) || (!tickInside && opposite)) {
  9615. posy = crossLabel.y - crossBox.height;
  9616. }
  9617. }
  9618. else {
  9619. posy = crossLabel.y - (crossBox.height / 2);
  9620. }
  9621. }
  9622. // check the edges
  9623. if (horiz) {
  9624. limit = {
  9625. left: left - crossBox.x,
  9626. right: left + this.width - crossBox.x
  9627. };
  9628. }
  9629. else {
  9630. limit = {
  9631. left: this.labelAlign === 'left' ? left : 0,
  9632. right: this.labelAlign === 'right' ?
  9633. left + this.width :
  9634. chart.chartWidth
  9635. };
  9636. }
  9637. // left edge
  9638. if (crossLabel.translateX < limit.left) {
  9639. offset = limit.left - crossLabel.translateX;
  9640. }
  9641. // right edge
  9642. if (crossLabel.translateX + crossBox.width >= limit.right) {
  9643. offset = -(crossLabel.translateX + crossBox.width - limit.right);
  9644. }
  9645. // show the crosslabel
  9646. crossLabel.attr({
  9647. x: posx + offset,
  9648. y: posy,
  9649. // First set x and y, then anchorX and anchorY, when box is actually
  9650. // calculated, #5702
  9651. anchorX: horiz ?
  9652. posx :
  9653. (this.opposite ? 0 : chart.chartWidth),
  9654. anchorY: horiz ?
  9655. (this.opposite ? chart.chartHeight : 0) :
  9656. posy + crossBox.height / 2
  9657. });
  9658. });
  9659. /* ************************************************************************** *
  9660. * Start value compare logic *
  9661. * ************************************************************************** */
  9662. /**
  9663. * Extend series.init by adding a method to modify the y value used for plotting
  9664. * on the y axis. This method is called both from the axis when finding dataMin
  9665. * and dataMax, and from the series.translate method.
  9666. *
  9667. * @ignore
  9668. * @function Highcharts.Series#init
  9669. */
  9670. seriesProto.init = function () {
  9671. // Call base method
  9672. seriesInit.apply(this, arguments);
  9673. // Set comparison mode
  9674. this.setCompare(this.options.compare);
  9675. };
  9676. /**
  9677. * Highstock only. Set the
  9678. * [compare](https://api.highcharts.com/highstock/plotOptions.series.compare)
  9679. * mode of the series after render time. In most cases it is more useful running
  9680. * {@link Axis#setCompare} on the X axis to update all its series.
  9681. *
  9682. * @function Highcharts.Series#setCompare
  9683. *
  9684. * @param {string} [compare]
  9685. * Can be one of `null` (default), `"percent"` or `"value"`.
  9686. */
  9687. seriesProto.setCompare = function (compare) {
  9688. // Set or unset the modifyValue method
  9689. this.modifyValue = (compare === 'value' || compare === 'percent') ?
  9690. function (value, point) {
  9691. var compareValue = this.compareValue;
  9692. if (typeof value !== 'undefined' &&
  9693. typeof compareValue !== 'undefined') { // #2601, #5814
  9694. // Get the modified value
  9695. if (compare === 'value') {
  9696. value -= compareValue;
  9697. // Compare percent
  9698. }
  9699. else {
  9700. value = 100 * (value / compareValue) -
  9701. (this.options.compareBase === 100 ? 0 : 100);
  9702. }
  9703. // record for tooltip etc.
  9704. if (point) {
  9705. point.change = value;
  9706. }
  9707. return value;
  9708. }
  9709. return 0;
  9710. } :
  9711. null;
  9712. // Survive to export, #5485
  9713. this.userOptions.compare = compare;
  9714. // Mark dirty
  9715. if (this.chart.hasRendered) {
  9716. this.isDirty = true;
  9717. }
  9718. };
  9719. /**
  9720. * Extend series.processData by finding the first y value in the plot area,
  9721. * used for comparing the following values
  9722. *
  9723. * @ignore
  9724. * @function Highcharts.Series#processData
  9725. */
  9726. seriesProto.processData = function (force) {
  9727. var series = this,
  9728. i,
  9729. keyIndex = -1,
  9730. processedXData,
  9731. processedYData,
  9732. compareStart = series.options.compareStart === true ? 0 : 1,
  9733. length,
  9734. compareValue;
  9735. // call base method
  9736. seriesProcessData.apply(this, arguments);
  9737. if (series.xAxis && series.processedYData) { // not pies
  9738. // local variables
  9739. processedXData = series.processedXData;
  9740. processedYData = series.processedYData;
  9741. length = processedYData.length;
  9742. // For series with more than one value (range, OHLC etc), compare
  9743. // against close or the pointValKey (#4922, #3112, #9854)
  9744. if (series.pointArrayMap) {
  9745. keyIndex = series.pointArrayMap.indexOf(series.options.pointValKey || series.pointValKey || 'y');
  9746. }
  9747. // find the first value for comparison
  9748. for (i = 0; i < length - compareStart; i++) {
  9749. compareValue = processedYData[i] && keyIndex > -1 ?
  9750. processedYData[i][keyIndex] :
  9751. processedYData[i];
  9752. if (isNumber(compareValue) &&
  9753. processedXData[i + compareStart] >=
  9754. series.xAxis.min &&
  9755. compareValue !== 0) {
  9756. series.compareValue = compareValue;
  9757. break;
  9758. }
  9759. }
  9760. }
  9761. return;
  9762. };
  9763. // Modify series extremes
  9764. addEvent(Series, 'afterGetExtremes', function (e) {
  9765. var dataExtremes = e.dataExtremes;
  9766. if (this.modifyValue && dataExtremes) {
  9767. var extremes = [
  9768. this.modifyValue(dataExtremes.dataMin),
  9769. this.modifyValue(dataExtremes.dataMax)
  9770. ];
  9771. dataExtremes.dataMin = arrayMin(extremes);
  9772. dataExtremes.dataMax = arrayMax(extremes);
  9773. }
  9774. });
  9775. /**
  9776. * Highstock only. Set the compare mode on all series belonging to an Y axis
  9777. * after render time.
  9778. *
  9779. * @see [series.plotOptions.compare](https://api.highcharts.com/highstock/series.plotOptions.compare)
  9780. *
  9781. * @sample stock/members/axis-setcompare/
  9782. * Set compoare
  9783. *
  9784. * @function Highcharts.Axis#setCompare
  9785. *
  9786. * @param {string} [compare]
  9787. * The compare mode. Can be one of `null` (default), `"value"` or
  9788. * `"percent"`.
  9789. *
  9790. * @param {boolean} [redraw=true]
  9791. * Whether to redraw the chart or to wait for a later call to
  9792. * {@link Chart#redraw}.
  9793. */
  9794. Axis.prototype.setCompare = function (compare, redraw) {
  9795. if (!this.isXAxis) {
  9796. this.series.forEach(function (series) {
  9797. series.setCompare(compare);
  9798. });
  9799. if (pick(redraw, true)) {
  9800. this.chart.redraw();
  9801. }
  9802. }
  9803. };
  9804. /**
  9805. * Extend the tooltip formatter by adding support for the point.change variable
  9806. * as well as the changeDecimals option.
  9807. *
  9808. * @ignore
  9809. * @function Highcharts.Point#tooltipFormatter
  9810. *
  9811. * @param {string} pointFormat
  9812. */
  9813. Point.prototype.tooltipFormatter = function (pointFormat) {
  9814. var point = this;
  9815. var numberFormatter = point.series.chart.numberFormatter;
  9816. pointFormat = pointFormat.replace('{point.change}', (point.change > 0 ? '+' : '') + numberFormatter(point.change, pick(point.series.tooltipOptions.changeDecimals, 2)));
  9817. return pointTooltipFormatter.apply(this, [pointFormat]);
  9818. };
  9819. /* ************************************************************************** *
  9820. * End value compare logic *
  9821. * ************************************************************************** */
  9822. // Extend the Series prototype to create a separate series clip box. This is
  9823. // related to using multiple panes, and a future pane logic should incorporate
  9824. // this feature (#2754).
  9825. addEvent(Series, 'render', function () {
  9826. var chart = this.chart,
  9827. clipHeight;
  9828. // Only do this on not 3d (#2939, #5904) nor polar (#6057) charts, and only
  9829. // if the series type handles clipping in the animate method (#2975).
  9830. if (!(chart.is3d && chart.is3d()) &&
  9831. !chart.polar &&
  9832. this.xAxis &&
  9833. !this.xAxis.isRadial // Gauge, #6192
  9834. ) {
  9835. clipHeight = this.yAxis.len;
  9836. // Include xAxis line width (#8031) but only if the Y axis ends on the
  9837. // edge of the X axis (#11005).
  9838. if (this.xAxis.axisLine) {
  9839. var dist = chart.plotTop + chart.plotHeight -
  9840. this.yAxis.pos - this.yAxis.len,
  9841. lineHeightCorrection = Math.floor(this.xAxis.axisLine.strokeWidth() / 2);
  9842. if (dist >= 0) {
  9843. clipHeight -= Math.max(lineHeightCorrection - dist, 0);
  9844. }
  9845. }
  9846. // First render, initial clip box
  9847. if (!this.clipBox && this.animate) {
  9848. this.clipBox = merge(chart.clipBox);
  9849. this.clipBox.width = this.xAxis.len;
  9850. this.clipBox.height = clipHeight;
  9851. // On redrawing, resizing etc, update the clip rectangle
  9852. }
  9853. else if (chart[this.sharedClipKey]) {
  9854. // animate in case resize is done during initial animation
  9855. chart[this.sharedClipKey].animate({
  9856. width: this.xAxis.len,
  9857. height: clipHeight
  9858. });
  9859. // also change markers clip animation for consistency
  9860. // (marker clip rects should exist only on chart init)
  9861. if (chart[this.sharedClipKey + 'm']) {
  9862. chart[this.sharedClipKey + 'm'].animate({
  9863. width: this.xAxis.len
  9864. });
  9865. }
  9866. }
  9867. }
  9868. });
  9869. addEvent(Chart, 'update', function (e) {
  9870. var options = e.options;
  9871. // Use case: enabling scrollbar from a disabled state.
  9872. // Scrollbar needs to be initialized from a controller, Navigator in this
  9873. // case (#6615)
  9874. if ('scrollbar' in options && this.navigator) {
  9875. merge(true, this.options.scrollbar, options.scrollbar);
  9876. this.navigator.update({}, false);
  9877. delete options.scrollbar;
  9878. }
  9879. });
  9880. });
  9881. _registerModule(_modules, 'masters/modules/stock.src.js', [], function () {
  9882. });
  9883. }));