stock.src.js 356 KB

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