gantt.src.js 477 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532
  1. /**
  2. * @license Highcharts Gantt JS v8.1.2 (2020-06-16)
  3. *
  4. * Gantt series
  5. *
  6. * (c) 2016-2019 Lars A. V. Cabrera
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. factory['default'] = factory;
  14. module.exports = factory;
  15. } else if (typeof define === 'function' && define.amd) {
  16. define('highcharts/modules/gantt', ['highcharts'], function (Highcharts) {
  17. factory(Highcharts);
  18. factory.Highcharts = Highcharts;
  19. return factory;
  20. });
  21. } else {
  22. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  23. }
  24. }(function (Highcharts) {
  25. var _modules = Highcharts ? Highcharts._modules : {};
  26. function _registerModule(obj, path, args, fn) {
  27. if (!obj.hasOwnProperty(path)) {
  28. obj[path] = fn.apply(null, args);
  29. }
  30. }
  31. _registerModule(_modules, 'parts-gantt/Tree.js', [_modules['parts/Utilities.js']], function (U) {
  32. /* *
  33. *
  34. * (c) 2016-2020 Highsoft AS
  35. *
  36. * Authors: Jon Arild Nygard
  37. *
  38. * License: www.highcharts.com/license
  39. *
  40. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  41. *
  42. * */
  43. /* eslint no-console: 0 */
  44. var extend = U.extend, isNumber = U.isNumber, pick = U.pick;
  45. /**
  46. * Creates an object map from parent id to childrens index.
  47. *
  48. * @private
  49. * @function Highcharts.Tree#getListOfParents
  50. *
  51. * @param {Array<*>} data
  52. * List of points set in options. `Array.parent` is parent id of point.
  53. *
  54. * @param {Array<string>} ids
  55. * List of all point ids.
  56. *
  57. * @return {Highcharts.Dictionary<Array<*>>}
  58. * Map from parent id to children index in data
  59. */
  60. var getListOfParents = function (data, ids) {
  61. var listOfParents = data.reduce(function (prev, curr) {
  62. var parent = pick(curr.parent, '');
  63. if (typeof prev[parent] === 'undefined') {
  64. prev[parent] = [];
  65. }
  66. prev[parent].push(curr);
  67. return prev;
  68. }, {}), parents = Object.keys(listOfParents);
  69. // If parent does not exist, hoist parent to root of tree.
  70. parents.forEach(function (parent, list) {
  71. var children = listOfParents[parent];
  72. if ((parent !== '') && (ids.indexOf(parent) === -1)) {
  73. children.forEach(function (child) {
  74. list[''].push(child);
  75. });
  76. delete list[parent];
  77. }
  78. });
  79. return listOfParents;
  80. };
  81. var getNode = function (id, parent, level, data, mapOfIdToChildren, options) {
  82. var descendants = 0, height = 0, after = options && options.after, before = options && options.before, node = {
  83. data: data,
  84. depth: level - 1,
  85. id: id,
  86. level: level,
  87. parent: parent
  88. }, start, end, children;
  89. // Allow custom logic before the children has been created.
  90. if (typeof before === 'function') {
  91. before(node, options);
  92. }
  93. // Call getNode recursively on the children. Calulate the height of the
  94. // node, and the number of descendants.
  95. children = ((mapOfIdToChildren[id] || [])).map(function (child) {
  96. var node = getNode(child.id, id, (level + 1), child, mapOfIdToChildren, options), childStart = child.start, childEnd = (child.milestone === true ?
  97. childStart :
  98. child.end);
  99. // Start should be the lowest child.start.
  100. start = ((!isNumber(start) || childStart < start) ?
  101. childStart :
  102. start);
  103. // End should be the largest child.end.
  104. // If child is milestone, then use start as end.
  105. end = ((!isNumber(end) || childEnd > end) ?
  106. childEnd :
  107. end);
  108. descendants = descendants + 1 + node.descendants;
  109. height = Math.max(node.height + 1, height);
  110. return node;
  111. });
  112. // Calculate start and end for point if it is not already explicitly set.
  113. if (data) {
  114. data.start = pick(data.start, start);
  115. data.end = pick(data.end, end);
  116. }
  117. extend(node, {
  118. children: children,
  119. descendants: descendants,
  120. height: height
  121. });
  122. // Allow custom logic after the children has been created.
  123. if (typeof after === 'function') {
  124. after(node, options);
  125. }
  126. return node;
  127. };
  128. var getTree = function (data, options) {
  129. var ids = data.map(function (d) {
  130. return d.id;
  131. }), mapOfIdToChildren = getListOfParents(data, ids);
  132. return getNode('', null, 1, null, mapOfIdToChildren, options);
  133. };
  134. var Tree = {
  135. getListOfParents: getListOfParents,
  136. getNode: getNode,
  137. getTree: getTree
  138. };
  139. return Tree;
  140. });
  141. _registerModule(_modules, 'parts-gantt/TreeGridTick.js', [_modules['parts/Utilities.js']], function (U) {
  142. /* *
  143. *
  144. * (c) 2016 Highsoft AS
  145. * Authors: Jon Arild Nygard
  146. *
  147. * License: www.highcharts.com/license
  148. *
  149. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  150. *
  151. * */
  152. var addEvent = U.addEvent, defined = U.defined, isObject = U.isObject, isNumber = U.isNumber, pick = U.pick, wrap = U.wrap;
  153. /**
  154. * @private
  155. */
  156. var TreeGridTick;
  157. (function (TreeGridTick) {
  158. /* *
  159. *
  160. * Interfaces
  161. *
  162. * */
  163. /* *
  164. *
  165. * Variables
  166. *
  167. * */
  168. var applied = false;
  169. /* *
  170. *
  171. * Functions
  172. *
  173. * */
  174. /**
  175. * @private
  176. */
  177. function compose(TickClass) {
  178. if (!applied) {
  179. addEvent(TickClass, 'init', onInit);
  180. wrap(TickClass.prototype, 'getLabelPosition', wrapGetLabelPosition);
  181. wrap(TickClass.prototype, 'renderLabel', wrapRenderLabel);
  182. // backwards compatibility
  183. TickClass.prototype.collapse = function (redraw) {
  184. this.treeGrid.collapse(redraw);
  185. };
  186. TickClass.prototype.expand = function (redraw) {
  187. this.treeGrid.expand(redraw);
  188. };
  189. TickClass.prototype.toggleCollapse = function (redraw) {
  190. this.treeGrid.toggleCollapse(redraw);
  191. };
  192. applied = true;
  193. }
  194. }
  195. TreeGridTick.compose = compose;
  196. /**
  197. * @private
  198. */
  199. function onInit() {
  200. var tick = this;
  201. if (!tick.treeGrid) {
  202. tick.treeGrid = new Additions(tick);
  203. }
  204. }
  205. /**
  206. * @private
  207. */
  208. function onTickHover(label) {
  209. label.addClass('highcharts-treegrid-node-active');
  210. if (!label.renderer.styledMode) {
  211. label.css({
  212. textDecoration: 'underline'
  213. });
  214. }
  215. }
  216. /**
  217. * @private
  218. */
  219. function onTickHoverExit(label, options) {
  220. var css = defined(options.style) ? options.style : {};
  221. label.removeClass('highcharts-treegrid-node-active');
  222. if (!label.renderer.styledMode) {
  223. label.css({ textDecoration: css.textDecoration });
  224. }
  225. }
  226. /**
  227. * @private
  228. */
  229. function renderLabelIcon(tick, params) {
  230. var treeGrid = tick.treeGrid, isNew = !treeGrid.labelIcon, renderer = params.renderer, labelBox = params.xy, options = params.options, width = options.width, height = options.height, iconCenter = {
  231. x: labelBox.x - (width / 2) - options.padding,
  232. y: labelBox.y - (height / 2)
  233. }, rotation = params.collapsed ? 90 : 180, shouldRender = params.show && isNumber(iconCenter.y);
  234. var icon = treeGrid.labelIcon;
  235. if (!icon) {
  236. treeGrid.labelIcon = icon = renderer
  237. .path(renderer.symbols[options.type](options.x, options.y, width, height))
  238. .addClass('highcharts-label-icon')
  239. .add(params.group);
  240. }
  241. // Set the new position, and show or hide
  242. if (!shouldRender) {
  243. icon.attr({ y: -9999 }); // #1338
  244. }
  245. // Presentational attributes
  246. if (!renderer.styledMode) {
  247. icon
  248. .attr({
  249. 'stroke-width': 1,
  250. 'fill': pick(params.color, '#666666')
  251. })
  252. .css({
  253. cursor: 'pointer',
  254. stroke: options.lineColor,
  255. strokeWidth: options.lineWidth
  256. });
  257. }
  258. // Update the icon positions
  259. icon[isNew ? 'attr' : 'animate']({
  260. translateX: iconCenter.x,
  261. translateY: iconCenter.y,
  262. rotation: rotation
  263. });
  264. }
  265. /**
  266. * @private
  267. */
  268. function wrapGetLabelPosition(proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
  269. var tick = this, lbOptions = pick(tick.options && tick.options.labels, labelOptions), pos = tick.pos, axis = tick.axis, options = axis.options, isTreeGrid = options.type === 'treegrid', result = proceed.apply(tick, [x, y, label, horiz, lbOptions, tickmarkOffset, index, step]);
  270. var symbolOptions, indentation, mapOfPosToGridNode, node, level;
  271. if (isTreeGrid) {
  272. symbolOptions = (lbOptions && isObject(lbOptions.symbol, true) ?
  273. lbOptions.symbol :
  274. {});
  275. indentation = (lbOptions && isNumber(lbOptions.indentation) ?
  276. lbOptions.indentation :
  277. 0);
  278. mapOfPosToGridNode = axis.treeGrid.mapOfPosToGridNode;
  279. node = mapOfPosToGridNode && mapOfPosToGridNode[pos];
  280. level = (node && node.depth) || 1;
  281. result.x += (
  282. // Add space for symbols
  283. ((symbolOptions.width) + (symbolOptions.padding * 2)) +
  284. // Apply indentation
  285. ((level - 1) * indentation));
  286. }
  287. return result;
  288. }
  289. /**
  290. * @private
  291. */
  292. function wrapRenderLabel(proceed) {
  293. var tick = this, pos = tick.pos, axis = tick.axis, label = tick.label, mapOfPosToGridNode = axis.treeGrid.mapOfPosToGridNode, options = axis.options, labelOptions = pick(tick.options && tick.options.labels, options && options.labels), symbolOptions = (labelOptions && isObject(labelOptions.symbol, true) ?
  294. labelOptions.symbol :
  295. {}), node = mapOfPosToGridNode && mapOfPosToGridNode[pos], level = node && node.depth, isTreeGrid = options.type === 'treegrid', shouldRender = axis.tickPositions.indexOf(pos) > -1, prefixClassName = 'highcharts-treegrid-node-', styledMode = axis.chart.styledMode;
  296. var collapsed, addClassName, removeClassName;
  297. if (isTreeGrid && node) {
  298. // Add class name for hierarchical styling.
  299. if (label &&
  300. label.element) {
  301. label.addClass(prefixClassName + 'level-' + level);
  302. }
  303. }
  304. proceed.apply(tick, Array.prototype.slice.call(arguments, 1));
  305. if (isTreeGrid &&
  306. label &&
  307. label.element &&
  308. node &&
  309. node.descendants &&
  310. node.descendants > 0) {
  311. collapsed = axis.treeGrid.isCollapsed(node);
  312. renderLabelIcon(tick, {
  313. color: !styledMode && label.styles && label.styles.color || '',
  314. collapsed: collapsed,
  315. group: label.parentGroup,
  316. options: symbolOptions,
  317. renderer: label.renderer,
  318. show: shouldRender,
  319. xy: label.xy
  320. });
  321. // Add class name for the node.
  322. addClassName = prefixClassName +
  323. (collapsed ? 'collapsed' : 'expanded');
  324. removeClassName = prefixClassName +
  325. (collapsed ? 'expanded' : 'collapsed');
  326. label
  327. .addClass(addClassName)
  328. .removeClass(removeClassName);
  329. if (!styledMode) {
  330. label.css({
  331. cursor: 'pointer'
  332. });
  333. }
  334. // Add events to both label text and icon
  335. [label, tick.treeGrid.labelIcon].forEach(function (object) {
  336. if (object && !object.attachedTreeGridEvents) {
  337. // On hover
  338. addEvent(object.element, 'mouseover', function () {
  339. onTickHover(label);
  340. });
  341. // On hover out
  342. addEvent(object.element, 'mouseout', function () {
  343. onTickHoverExit(label, labelOptions);
  344. });
  345. addEvent(object.element, 'click', function () {
  346. tick.treeGrid.toggleCollapse();
  347. });
  348. object.attachedTreeGridEvents = true;
  349. }
  350. });
  351. }
  352. }
  353. /* *
  354. *
  355. * Classes
  356. *
  357. * */
  358. /**
  359. * @private
  360. * @class
  361. */
  362. var Additions = /** @class */ (function () {
  363. /* *
  364. *
  365. * Constructors
  366. *
  367. * */
  368. /**
  369. * @private
  370. */
  371. function Additions(tick) {
  372. this.tick = tick;
  373. }
  374. /* *
  375. *
  376. * Functions
  377. *
  378. * */
  379. /**
  380. * Collapse the grid cell. Used when axis is of type treegrid.
  381. *
  382. * @see gantt/treegrid-axis/collapsed-dynamically/demo.js
  383. *
  384. * @private
  385. * @function Highcharts.Tick#collapse
  386. *
  387. * @param {boolean} [redraw=true]
  388. * Whether to redraw the chart or wait for an explicit call to
  389. * {@link Highcharts.Chart#redraw}
  390. */
  391. Additions.prototype.collapse = function (redraw) {
  392. var tick = this.tick, axis = tick.axis, brokenAxis = axis.brokenAxis;
  393. if (brokenAxis &&
  394. axis.treeGrid.mapOfPosToGridNode) {
  395. var pos = tick.pos, node = axis.treeGrid.mapOfPosToGridNode[pos], breaks = axis.treeGrid.collapse(node);
  396. brokenAxis.setBreaks(breaks, pick(redraw, true));
  397. }
  398. };
  399. /**
  400. * Expand the grid cell. Used when axis is of type treegrid.
  401. *
  402. * @see gantt/treegrid-axis/collapsed-dynamically/demo.js
  403. *
  404. * @private
  405. * @function Highcharts.Tick#expand
  406. *
  407. * @param {boolean} [redraw=true]
  408. * Whether to redraw the chart or wait for an explicit call to
  409. * {@link Highcharts.Chart#redraw}
  410. */
  411. Additions.prototype.expand = function (redraw) {
  412. var tick = this.tick, axis = tick.axis, brokenAxis = axis.brokenAxis;
  413. if (brokenAxis &&
  414. axis.treeGrid.mapOfPosToGridNode) {
  415. var pos = tick.pos, node = axis.treeGrid.mapOfPosToGridNode[pos], breaks = axis.treeGrid.expand(node);
  416. brokenAxis.setBreaks(breaks, pick(redraw, true));
  417. }
  418. };
  419. /**
  420. * Toggle the collapse/expand state of the grid cell. Used when axis is
  421. * of type treegrid.
  422. *
  423. * @see gantt/treegrid-axis/collapsed-dynamically/demo.js
  424. *
  425. * @private
  426. * @function Highcharts.Tick#toggleCollapse
  427. *
  428. * @param {boolean} [redraw=true]
  429. * Whether to redraw the chart or wait for an explicit call to
  430. * {@link Highcharts.Chart#redraw}
  431. */
  432. Additions.prototype.toggleCollapse = function (redraw) {
  433. var tick = this.tick, axis = tick.axis, brokenAxis = axis.brokenAxis;
  434. if (brokenAxis &&
  435. axis.treeGrid.mapOfPosToGridNode) {
  436. var pos = tick.pos, node = axis.treeGrid.mapOfPosToGridNode[pos], breaks = axis.treeGrid.toggleCollapse(node);
  437. brokenAxis.setBreaks(breaks, pick(redraw, true));
  438. }
  439. };
  440. return Additions;
  441. }());
  442. TreeGridTick.Additions = Additions;
  443. })(TreeGridTick || (TreeGridTick = {}));
  444. return TreeGridTick;
  445. });
  446. _registerModule(_modules, 'mixins/tree-series.js', [_modules['parts/Color.js'], _modules['parts/Utilities.js']], function (Color, U) {
  447. /* *
  448. *
  449. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  450. *
  451. * */
  452. var extend = U.extend, isArray = U.isArray, isNumber = U.isNumber, isObject = U.isObject, merge = U.merge, pick = U.pick;
  453. var isBoolean = function (x) {
  454. return typeof x === 'boolean';
  455. }, isFn = function (x) {
  456. return typeof x === 'function';
  457. };
  458. /* eslint-disable valid-jsdoc */
  459. /**
  460. * @todo Combine buildTree and buildNode with setTreeValues
  461. * @todo Remove logic from Treemap and make it utilize this mixin.
  462. * @private
  463. */
  464. var setTreeValues = function setTreeValues(tree, options) {
  465. var before = options.before, idRoot = options.idRoot, mapIdToNode = options.mapIdToNode, nodeRoot = mapIdToNode[idRoot], levelIsConstant = (isBoolean(options.levelIsConstant) ?
  466. options.levelIsConstant :
  467. true), points = options.points, point = points[tree.i], optionsPoint = point && point.options || {}, childrenTotal = 0, children = [], value;
  468. extend(tree, {
  469. levelDynamic: tree.level - (levelIsConstant ? 0 : nodeRoot.level),
  470. name: pick(point && point.name, ''),
  471. visible: (idRoot === tree.id ||
  472. (isBoolean(options.visible) ? options.visible : false))
  473. });
  474. if (isFn(before)) {
  475. tree = before(tree, options);
  476. }
  477. // First give the children some values
  478. tree.children.forEach(function (child, i) {
  479. var newOptions = extend({}, options);
  480. extend(newOptions, {
  481. index: i,
  482. siblings: tree.children.length,
  483. visible: tree.visible
  484. });
  485. child = setTreeValues(child, newOptions);
  486. children.push(child);
  487. if (child.visible) {
  488. childrenTotal += child.val;
  489. }
  490. });
  491. tree.visible = childrenTotal > 0 || tree.visible;
  492. // Set the values
  493. value = pick(optionsPoint.value, childrenTotal);
  494. extend(tree, {
  495. children: children,
  496. childrenTotal: childrenTotal,
  497. isLeaf: tree.visible && !childrenTotal,
  498. val: value
  499. });
  500. return tree;
  501. };
  502. /**
  503. * @private
  504. */
  505. var getColor = function getColor(node, options) {
  506. var index = options.index, mapOptionsToLevel = options.mapOptionsToLevel, parentColor = options.parentColor, parentColorIndex = options.parentColorIndex, series = options.series, colors = options.colors, siblings = options.siblings, points = series.points, getColorByPoint, chartOptionsChart = series.chart.options.chart, point, level, colorByPoint, colorIndexByPoint, color, colorIndex;
  507. /**
  508. * @private
  509. */
  510. function variation(color) {
  511. var colorVariation = level && level.colorVariation;
  512. if (colorVariation) {
  513. if (colorVariation.key === 'brightness') {
  514. return Color.parse(color).brighten(colorVariation.to * (index / siblings)).get();
  515. }
  516. }
  517. return color;
  518. }
  519. if (node) {
  520. point = points[node.i];
  521. level = mapOptionsToLevel[node.level] || {};
  522. getColorByPoint = point && level.colorByPoint;
  523. if (getColorByPoint) {
  524. colorIndexByPoint = point.index % (colors ?
  525. colors.length :
  526. chartOptionsChart.colorCount);
  527. colorByPoint = colors && colors[colorIndexByPoint];
  528. }
  529. // Select either point color, level color or inherited color.
  530. if (!series.chart.styledMode) {
  531. color = pick(point && point.options.color, level && level.color, colorByPoint, parentColor && variation(parentColor), series.color);
  532. }
  533. colorIndex = pick(point && point.options.colorIndex, level && level.colorIndex, colorIndexByPoint, parentColorIndex, options.colorIndex);
  534. }
  535. return {
  536. color: color,
  537. colorIndex: colorIndex
  538. };
  539. };
  540. /**
  541. * Creates a map from level number to its given options.
  542. *
  543. * @private
  544. * @function getLevelOptions
  545. * @param {object} params
  546. * Object containing parameters.
  547. * - `defaults` Object containing default options. The default options
  548. * are merged with the userOptions to get the final options for a
  549. * specific level.
  550. * - `from` The lowest level number.
  551. * - `levels` User options from series.levels.
  552. * - `to` The highest level number.
  553. * @return {Highcharts.Dictionary<object>|null}
  554. * Returns a map from level number to its given options.
  555. */
  556. var getLevelOptions = function getLevelOptions(params) {
  557. var result = null, defaults, converted, i, from, to, levels;
  558. if (isObject(params)) {
  559. result = {};
  560. from = isNumber(params.from) ? params.from : 1;
  561. levels = params.levels;
  562. converted = {};
  563. defaults = isObject(params.defaults) ? params.defaults : {};
  564. if (isArray(levels)) {
  565. converted = levels.reduce(function (obj, item) {
  566. var level, levelIsConstant, options;
  567. if (isObject(item) && isNumber(item.level)) {
  568. options = merge({}, item);
  569. levelIsConstant = (isBoolean(options.levelIsConstant) ?
  570. options.levelIsConstant :
  571. defaults.levelIsConstant);
  572. // Delete redundant properties.
  573. delete options.levelIsConstant;
  574. delete options.level;
  575. // Calculate which level these options apply to.
  576. level = item.level + (levelIsConstant ? 0 : from - 1);
  577. if (isObject(obj[level])) {
  578. extend(obj[level], options);
  579. }
  580. else {
  581. obj[level] = options;
  582. }
  583. }
  584. return obj;
  585. }, {});
  586. }
  587. to = isNumber(params.to) ? params.to : 1;
  588. for (i = 0; i <= to; i++) {
  589. result[i] = merge({}, defaults, isObject(converted[i]) ? converted[i] : {});
  590. }
  591. }
  592. return result;
  593. };
  594. /**
  595. * Update the rootId property on the series. Also makes sure that it is
  596. * accessible to exporting.
  597. *
  598. * @private
  599. * @function updateRootId
  600. *
  601. * @param {object} series
  602. * The series to operate on.
  603. *
  604. * @return {string}
  605. * Returns the resulting rootId after update.
  606. */
  607. var updateRootId = function (series) {
  608. var rootId, options;
  609. if (isObject(series)) {
  610. // Get the series options.
  611. options = isObject(series.options) ? series.options : {};
  612. // Calculate the rootId.
  613. rootId = pick(series.rootNode, options.rootId, '');
  614. // Set rootId on series.userOptions to pick it up in exporting.
  615. if (isObject(series.userOptions)) {
  616. series.userOptions.rootId = rootId;
  617. }
  618. // Set rootId on series to pick it up on next update.
  619. series.rootNode = rootId;
  620. }
  621. return rootId;
  622. };
  623. var result = {
  624. getColor: getColor,
  625. getLevelOptions: getLevelOptions,
  626. setTreeValues: setTreeValues,
  627. updateRootId: updateRootId
  628. };
  629. return result;
  630. });
  631. _registerModule(_modules, 'parts-gantt/GridAxis.js', [_modules['parts/Axis.js'], _modules['parts/Globals.js'], _modules['parts/Options.js'], _modules['parts/Tick.js'], _modules['parts/Utilities.js']], function (Axis, H, O, Tick, U) {
  632. /* *
  633. *
  634. * (c) 2016 Highsoft AS
  635. * Authors: Lars A. V. Cabrera
  636. *
  637. * License: www.highcharts.com/license
  638. *
  639. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  640. *
  641. * */
  642. var dateFormat = O.dateFormat;
  643. var addEvent = U.addEvent, defined = U.defined, erase = U.erase, find = U.find, isArray = U.isArray, isNumber = U.isNumber, merge = U.merge, pick = U.pick, timeUnits = U.timeUnits, wrap = U.wrap;
  644. var argsToArray = function (args) {
  645. return Array.prototype.slice.call(args, 1);
  646. }, isObject = function (x) {
  647. // Always use strict mode
  648. return U.isObject(x, true);
  649. }, Chart = H.Chart;
  650. var applyGridOptions = function applyGridOptions(axis) {
  651. var options = axis.options;
  652. // Center-align by default
  653. if (!options.labels) {
  654. options.labels = {};
  655. }
  656. options.labels.align = pick(options.labels.align, 'center');
  657. // @todo: Check against tickLabelPlacement between/on etc
  658. /* Prevents adding the last tick label if the axis is not a category
  659. axis.
  660. Since numeric labels are normally placed at starts and ends of a
  661. range of value, and this module makes the label point at the value,
  662. an "extra" label would appear. */
  663. if (!axis.categories) {
  664. options.showLastLabel = false;
  665. }
  666. // Prevents rotation of labels when squished, as rotating them would not
  667. // help.
  668. axis.labelRotation = 0;
  669. options.labels.rotation = 0;
  670. };
  671. /**
  672. * Set grid options for the axis labels. Requires Highcharts Gantt.
  673. *
  674. * @since 6.2.0
  675. * @product gantt
  676. * @apioption xAxis.grid
  677. */
  678. /**
  679. * Enable grid on the axis labels. Defaults to true for Gantt charts.
  680. *
  681. * @type {boolean}
  682. * @default true
  683. * @since 6.2.0
  684. * @product gantt
  685. * @apioption xAxis.grid.enabled
  686. */
  687. /**
  688. * Set specific options for each column (or row for horizontal axes) in the
  689. * grid. Each extra column/row is its own axis, and the axis options can be set
  690. * here.
  691. *
  692. * @sample gantt/demo/left-axis-table
  693. * Left axis as a table
  694. *
  695. * @type {Array<Highcharts.XAxisOptions>}
  696. * @apioption xAxis.grid.columns
  697. */
  698. /**
  699. * Set border color for the label grid lines.
  700. *
  701. * @type {Highcharts.ColorString}
  702. * @apioption xAxis.grid.borderColor
  703. */
  704. /**
  705. * Set border width of the label grid lines.
  706. *
  707. * @type {number}
  708. * @default 1
  709. * @apioption xAxis.grid.borderWidth
  710. */
  711. /**
  712. * Set cell height for grid axis labels. By default this is calculated from font
  713. * size. This option only applies to horizontal axes.
  714. *
  715. * @sample gantt/grid-axis/cellheight
  716. * Gant chart with custom cell height
  717. * @type {number}
  718. * @apioption xAxis.grid.cellHeight
  719. */
  720. ''; // detach doclets above
  721. /**
  722. * Get the largest label width and height.
  723. *
  724. * @private
  725. * @function Highcharts.Axis#getMaxLabelDimensions
  726. *
  727. * @param {Highcharts.Dictionary<Highcharts.Tick>} ticks
  728. * All the ticks on one axis.
  729. *
  730. * @param {Array<number|string>} tickPositions
  731. * All the tick positions on one axis.
  732. *
  733. * @return {Highcharts.SizeObject}
  734. * Object containing the properties height and width.
  735. *
  736. * @todo Move this to the generic axis implementation, as it is used there.
  737. */
  738. Axis.prototype.getMaxLabelDimensions = function (ticks, tickPositions) {
  739. var dimensions = {
  740. width: 0,
  741. height: 0
  742. };
  743. tickPositions.forEach(function (pos) {
  744. var tick = ticks[pos], tickHeight = 0, tickWidth = 0, label;
  745. if (isObject(tick)) {
  746. label = isObject(tick.label) ? tick.label : {};
  747. // Find width and height of tick
  748. tickHeight = label.getBBox ? label.getBBox().height : 0;
  749. if (label.textStr && !isNumber(label.textPxLength)) {
  750. label.textPxLength = label.getBBox().width;
  751. }
  752. tickWidth = isNumber(label.textPxLength) ?
  753. // Math.round ensures crisp lines
  754. Math.round(label.textPxLength) :
  755. 0;
  756. // Update the result if width and/or height are larger
  757. dimensions.height = Math.max(tickHeight, dimensions.height);
  758. dimensions.width = Math.max(tickWidth, dimensions.width);
  759. }
  760. });
  761. return dimensions;
  762. };
  763. // Adds week date format
  764. H.dateFormats.W = function (timestamp) {
  765. var d = new this.Date(timestamp);
  766. var firstDay = (this.get('Day', d) + 6) % 7;
  767. var thursday = new this.Date(d.valueOf());
  768. this.set('Date', thursday, this.get('Date', d) - firstDay + 3);
  769. var firstThursday = new this.Date(this.get('FullYear', thursday), 0, 1);
  770. if (this.get('Day', firstThursday) !== 4) {
  771. this.set('Month', d, 0);
  772. this.set('Date', d, 1 + (11 - this.get('Day', firstThursday)) % 7);
  773. }
  774. return (1 +
  775. Math.floor((thursday.valueOf() - firstThursday.valueOf()) / 604800000)).toString();
  776. };
  777. // First letter of the day of the week, e.g. 'M' for 'Monday'.
  778. H.dateFormats.E = function (timestamp) {
  779. return dateFormat('%a', timestamp, true).charAt(0);
  780. };
  781. /* eslint-disable no-invalid-this */
  782. addEvent(Chart, 'afterSetChartSize', function () {
  783. this.axes.forEach(function (axis) {
  784. (axis.grid && axis.grid.columns || []).forEach(function (column) {
  785. column.setAxisSize();
  786. column.setAxisTranslation();
  787. });
  788. });
  789. });
  790. // Center tick labels in cells.
  791. addEvent(Tick, 'afterGetLabelPosition', function (e) {
  792. var tick = this, label = tick.label, axis = tick.axis, reversed = axis.reversed, chart = axis.chart, options = axis.options, gridOptions = options.grid || {}, labelOpts = axis.options.labels, align = labelOpts.align,
  793. // verticalAlign is currently not supported for axis.labels.
  794. verticalAlign = 'middle', // labelOpts.verticalAlign,
  795. side = GridAxis.Side[axis.side], tickmarkOffset = e.tickmarkOffset, tickPositions = axis.tickPositions, tickPos = tick.pos - tickmarkOffset, nextTickPos = (isNumber(tickPositions[e.index + 1]) ?
  796. tickPositions[e.index + 1] - tickmarkOffset :
  797. axis.max + tickmarkOffset), tickSize = axis.tickSize('tick'), tickWidth = tickSize ? tickSize[0] : 0, crispCorr = tickSize ? tickSize[1] / 2 : 0, labelHeight, lblMetrics, lines, bottom, top, left, right;
  798. // Only center tick labels in grid axes
  799. if (gridOptions.enabled === true) {
  800. // Calculate top and bottom positions of the cell.
  801. if (side === 'top') {
  802. bottom = axis.top + axis.offset;
  803. top = bottom - tickWidth;
  804. }
  805. else if (side === 'bottom') {
  806. top = chart.chartHeight - axis.bottom + axis.offset;
  807. bottom = top + tickWidth;
  808. }
  809. else {
  810. bottom = axis.top + axis.len - axis.translate(reversed ? nextTickPos : tickPos);
  811. top = axis.top + axis.len - axis.translate(reversed ? tickPos : nextTickPos);
  812. }
  813. // Calculate left and right positions of the cell.
  814. if (side === 'right') {
  815. left = chart.chartWidth - axis.right + axis.offset;
  816. right = left + tickWidth;
  817. }
  818. else if (side === 'left') {
  819. right = axis.left + axis.offset;
  820. left = right - tickWidth;
  821. }
  822. else {
  823. left = Math.round(axis.left + axis.translate(reversed ? nextTickPos : tickPos)) - crispCorr;
  824. right = Math.round(axis.left + axis.translate(reversed ? tickPos : nextTickPos)) - crispCorr;
  825. }
  826. tick.slotWidth = right - left;
  827. // Calculate the positioning of the label based on
  828. // alignment.
  829. e.pos.x = (align === 'left' ?
  830. left :
  831. align === 'right' ?
  832. right :
  833. left + ((right - left) / 2) // default to center
  834. );
  835. e.pos.y = (verticalAlign === 'top' ?
  836. top :
  837. verticalAlign === 'bottom' ?
  838. bottom :
  839. top + ((bottom - top) / 2) // default to middle
  840. );
  841. lblMetrics = chart.renderer.fontMetrics(labelOpts.style.fontSize, label.element);
  842. labelHeight = label.getBBox().height;
  843. // Adjustment to y position to align the label correctly.
  844. // Would be better to have a setter or similar for this.
  845. if (!labelOpts.useHTML) {
  846. lines = Math.round(labelHeight / lblMetrics.h);
  847. e.pos.y += (
  848. // Center the label
  849. // TODO: why does this actually center the label?
  850. ((lblMetrics.b - (lblMetrics.h - lblMetrics.f)) / 2) +
  851. // Adjust for height of additional lines.
  852. -(((lines - 1) * lblMetrics.h) / 2));
  853. }
  854. else {
  855. e.pos.y += (
  856. // Readjust yCorr in htmlUpdateTransform
  857. lblMetrics.b +
  858. // Adjust for height of html label
  859. -(labelHeight / 2));
  860. }
  861. e.pos.x += (axis.horiz && labelOpts.x || 0);
  862. }
  863. });
  864. /* eslint-enable no-invalid-this */
  865. /**
  866. * Additions for grid axes.
  867. * @private
  868. * @class
  869. */
  870. var GridAxisAdditions = /** @class */ (function () {
  871. /* *
  872. *
  873. * Constructors
  874. *
  875. * */
  876. function GridAxisAdditions(axis) {
  877. this.axis = axis;
  878. }
  879. /* *
  880. *
  881. * Functions
  882. *
  883. * */
  884. /**
  885. * Checks if an axis is the outer axis in its dimension. Since
  886. * axes are placed outwards in order, the axis with the highest
  887. * index is the outermost axis.
  888. *
  889. * Example: If there are multiple x-axes at the top of the chart,
  890. * this function returns true if the axis supplied is the last
  891. * of the x-axes.
  892. *
  893. * @private
  894. *
  895. * @return {boolean}
  896. * True if the axis is the outermost axis in its dimension; false if
  897. * not.
  898. */
  899. GridAxisAdditions.prototype.isOuterAxis = function () {
  900. var axis = this.axis;
  901. var chart = axis.chart;
  902. var columnIndex = axis.grid.columnIndex;
  903. var columns = (axis.linkedParent && axis.linkedParent.grid.columns ||
  904. axis.grid.columns);
  905. var parentAxis = columnIndex ? axis.linkedParent : axis;
  906. var thisIndex = -1, lastIndex = 0;
  907. chart[axis.coll].forEach(function (otherAxis, index) {
  908. if (otherAxis.side === axis.side && !otherAxis.options.isInternal) {
  909. lastIndex = index;
  910. if (otherAxis === parentAxis) {
  911. // Get the index of the axis in question
  912. thisIndex = index;
  913. }
  914. }
  915. });
  916. return (lastIndex === thisIndex &&
  917. (isNumber(columnIndex) ? columns.length === columnIndex : true));
  918. };
  919. return GridAxisAdditions;
  920. }());
  921. /**
  922. * Axis with grid support.
  923. * @private
  924. * @class
  925. */
  926. var GridAxis = /** @class */ (function () {
  927. function GridAxis() {
  928. }
  929. /* *
  930. *
  931. * Static Functions
  932. *
  933. * */
  934. /* eslint-disable valid-jsdoc */
  935. /**
  936. * Extends axis class with grid support.
  937. * @private
  938. */
  939. GridAxis.compose = function (AxisClass) {
  940. Axis.keepProps.push('grid');
  941. wrap(AxisClass.prototype, 'unsquish', GridAxis.wrapUnsquish);
  942. // Add event handlers
  943. addEvent(AxisClass, 'init', GridAxis.onInit);
  944. addEvent(AxisClass, 'afterGetOffset', GridAxis.onAfterGetOffset);
  945. addEvent(AxisClass, 'afterGetTitlePosition', GridAxis.onAfterGetTitlePosition);
  946. addEvent(AxisClass, 'afterInit', GridAxis.onAfterInit);
  947. addEvent(AxisClass, 'afterRender', GridAxis.onAfterRender);
  948. addEvent(AxisClass, 'afterSetAxisTranslation', GridAxis.onAfterSetAxisTranslation);
  949. addEvent(AxisClass, 'afterSetOptions', GridAxis.onAfterSetOptions);
  950. addEvent(AxisClass, 'afterSetOptions', GridAxis.onAfterSetOptions2);
  951. addEvent(AxisClass, 'afterSetScale', GridAxis.onAfterSetScale);
  952. addEvent(AxisClass, 'afterTickSize', GridAxis.onAfterTickSize);
  953. addEvent(AxisClass, 'trimTicks', GridAxis.onTrimTicks);
  954. addEvent(AxisClass, 'destroy', GridAxis.onDestroy);
  955. };
  956. /**
  957. * Handle columns and getOffset.
  958. * @private
  959. */
  960. GridAxis.onAfterGetOffset = function () {
  961. var grid = this.grid;
  962. (grid && grid.columns || []).forEach(function (column) {
  963. column.getOffset();
  964. });
  965. };
  966. /**
  967. * @private
  968. */
  969. GridAxis.onAfterGetTitlePosition = function (e) {
  970. var axis = this;
  971. var options = axis.options;
  972. var gridOptions = options.grid || {};
  973. if (gridOptions.enabled === true) {
  974. // compute anchor points for each of the title align options
  975. var title = axis.axisTitle, axisHeight = axis.height, horiz = axis.horiz, axisLeft = axis.left, offset = axis.offset, opposite = axis.opposite, _a = axis.options.title, axisTitleOptions = _a === void 0 ? {} : _a, axisTop = axis.top, axisWidth = axis.width;
  976. var tickSize = axis.tickSize();
  977. var titleWidth = title && title.getBBox().width;
  978. var xOption = axisTitleOptions.x || 0;
  979. var yOption = axisTitleOptions.y || 0;
  980. var titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
  981. var titleFontSize = axis.chart.renderer.fontMetrics(axisTitleOptions.style &&
  982. axisTitleOptions.style.fontSize, title).f;
  983. var crispCorr = tickSize ? tickSize[0] / 2 : 0;
  984. // TODO account for alignment
  985. // the position in the perpendicular direction of the axis
  986. var offAxis = ((horiz ? axisTop + axisHeight : axisLeft) +
  987. (horiz ? 1 : -1) * // horizontal axis reverses the margin
  988. (opposite ? -1 : 1) * // so does opposite axes
  989. crispCorr +
  990. (axis.side === GridAxis.Side.bottom ? titleFontSize : 0));
  991. e.titlePosition.x = horiz ?
  992. axisLeft - titleWidth / 2 - titleMargin + xOption :
  993. offAxis + (opposite ? axisWidth : 0) + offset + xOption;
  994. e.titlePosition.y = horiz ?
  995. (offAxis -
  996. (opposite ? axisHeight : 0) +
  997. (opposite ? titleFontSize : -titleFontSize) / 2 +
  998. offset +
  999. yOption) :
  1000. axisTop - titleMargin + yOption;
  1001. }
  1002. };
  1003. /**
  1004. * @private
  1005. */
  1006. GridAxis.onAfterInit = function () {
  1007. var axis = this;
  1008. var chart = axis.chart, _a = axis.options.grid, gridOptions = _a === void 0 ? {} : _a, userOptions = axis.userOptions;
  1009. if (gridOptions.enabled) {
  1010. applyGridOptions(axis);
  1011. /* eslint-disable no-invalid-this */
  1012. // TODO: wrap the axis instead
  1013. wrap(axis, 'labelFormatter', function (proceed) {
  1014. var _a = this, axis = _a.axis, value = _a.value;
  1015. var tickPos = axis.tickPositions;
  1016. var series = (axis.isLinked ?
  1017. axis.linkedParent :
  1018. axis).series[0];
  1019. var isFirst = value === tickPos[0];
  1020. var isLast = value === tickPos[tickPos.length - 1];
  1021. var point = series && find(series.options.data, function (p) {
  1022. return p[axis.isXAxis ? 'x' : 'y'] === value;
  1023. });
  1024. // Make additional properties available for the
  1025. // formatter
  1026. this.isFirst = isFirst;
  1027. this.isLast = isLast;
  1028. this.point = point;
  1029. // Call original labelFormatter
  1030. return proceed.call(this);
  1031. });
  1032. /* eslint-enable no-invalid-this */
  1033. }
  1034. if (gridOptions.columns) {
  1035. var columns = axis.grid.columns = [], columnIndex = axis.grid.columnIndex = 0;
  1036. // Handle columns, each column is a grid axis
  1037. while (++columnIndex < gridOptions.columns.length) {
  1038. var columnOptions = merge(userOptions, gridOptions.columns[gridOptions.columns.length - columnIndex - 1], {
  1039. linkedTo: 0,
  1040. // Force to behave like category axis
  1041. type: 'category'
  1042. });
  1043. delete columnOptions.grid.columns; // Prevent recursion
  1044. var column = new Axis(axis.chart, columnOptions);
  1045. column.grid.isColumn = true;
  1046. column.grid.columnIndex = columnIndex;
  1047. // Remove column axis from chart axes array, and place it
  1048. // in the columns array.
  1049. erase(chart.axes, column);
  1050. erase(chart[axis.coll], column);
  1051. columns.push(column);
  1052. }
  1053. }
  1054. };
  1055. /**
  1056. * Draw an extra line on the far side of the outermost axis,
  1057. * creating floor/roof/wall of a grid. And some padding.
  1058. * ```
  1059. * Make this:
  1060. * (axis.min) __________________________ (axis.max)
  1061. * | | | | |
  1062. * Into this:
  1063. * (axis.min) __________________________ (axis.max)
  1064. * ___|____|____|____|____|__
  1065. * ```
  1066. * @private
  1067. */
  1068. GridAxis.onAfterRender = function () {
  1069. var axis = this;
  1070. var grid = axis.grid;
  1071. var options = axis.options;
  1072. var renderer = axis.chart.renderer;
  1073. var gridOptions = options.grid || {};
  1074. var yStartIndex, yEndIndex, xStartIndex, xEndIndex;
  1075. if (gridOptions.enabled === true) {
  1076. // @todo acutual label padding (top, bottom, left, right)
  1077. axis.maxLabelDimensions = axis.getMaxLabelDimensions(axis.ticks, axis.tickPositions);
  1078. // Remove right wall before rendering if updating
  1079. if (axis.rightWall) {
  1080. axis.rightWall.destroy();
  1081. }
  1082. /*
  1083. Draw an extra axis line on outer axes
  1084. >
  1085. Make this: |______|______|______|___
  1086. > _________________________
  1087. Into this: |______|______|______|__|
  1088. */
  1089. if (axis.grid && axis.grid.isOuterAxis() && axis.axisLine) {
  1090. var lineWidth = options.lineWidth;
  1091. if (lineWidth) {
  1092. var linePath = axis.getLinePath(lineWidth);
  1093. var startPoint = linePath[0];
  1094. var endPoint = linePath[1];
  1095. // Negate distance if top or left axis
  1096. // Subtract 1px to draw the line at the end of the tick
  1097. var tickLength = (axis.tickSize('tick') || [1])[0];
  1098. var distance = (tickLength - 1) * ((axis.side === GridAxis.Side.top ||
  1099. axis.side === GridAxis.Side.left) ? -1 : 1);
  1100. // If axis is horizontal, reposition line path vertically
  1101. if (startPoint[0] === 'M' && endPoint[0] === 'L') {
  1102. if (axis.horiz) {
  1103. startPoint[2] += distance;
  1104. endPoint[2] += distance;
  1105. }
  1106. else {
  1107. // If axis is vertical, reposition line path
  1108. // horizontally
  1109. startPoint[1] += distance;
  1110. endPoint[1] += distance;
  1111. }
  1112. }
  1113. if (!axis.grid.axisLineExtra) {
  1114. axis.grid.axisLineExtra = renderer
  1115. .path(linePath)
  1116. .attr({
  1117. zIndex: 7
  1118. })
  1119. .addClass('highcharts-axis-line')
  1120. .add(axis.axisGroup);
  1121. if (!renderer.styledMode) {
  1122. axis.grid.axisLineExtra.attr({
  1123. stroke: options.lineColor,
  1124. 'stroke-width': lineWidth
  1125. });
  1126. }
  1127. }
  1128. else {
  1129. axis.grid.axisLineExtra.animate({
  1130. d: linePath
  1131. });
  1132. }
  1133. // show or hide the line depending on
  1134. // options.showEmpty
  1135. axis.axisLine[axis.showAxis ? 'show' : 'hide'](true);
  1136. }
  1137. }
  1138. (grid && grid.columns || []).forEach(function (column) {
  1139. column.render();
  1140. });
  1141. }
  1142. };
  1143. /**
  1144. * @private
  1145. */
  1146. GridAxis.onAfterSetAxisTranslation = function () {
  1147. var axis = this;
  1148. var tickInfo = axis.tickPositions && axis.tickPositions.info;
  1149. var options = axis.options;
  1150. var gridOptions = options.grid || {};
  1151. var userLabels = axis.userOptions.labels || {};
  1152. if (axis.horiz) {
  1153. if (gridOptions.enabled === true) {
  1154. axis.series.forEach(function (series) {
  1155. series.options.pointRange = 0;
  1156. });
  1157. }
  1158. // Lower level time ticks, like hours or minutes, represent
  1159. // points in time and not ranges. These should be aligned
  1160. // left in the grid cell by default. The same applies to
  1161. // years of higher order.
  1162. if (tickInfo &&
  1163. options.dateTimeLabelFormats &&
  1164. options.labels &&
  1165. !defined(userLabels.align) &&
  1166. (options.dateTimeLabelFormats[tickInfo.unitName].range === false ||
  1167. tickInfo.count > 1 // years
  1168. )) {
  1169. options.labels.align = 'left';
  1170. if (!defined(userLabels.x)) {
  1171. options.labels.x = 3;
  1172. }
  1173. }
  1174. }
  1175. };
  1176. /**
  1177. * Creates a left and right wall on horizontal axes:
  1178. * - Places leftmost tick at the start of the axis, to create a left
  1179. * wall
  1180. * - Ensures that the rightmost tick is at the end of the axis, to
  1181. * create a right wall.
  1182. * @private
  1183. */
  1184. GridAxis.onAfterSetOptions = function (e) {
  1185. var options = this.options, userOptions = e.userOptions, gridAxisOptions, gridOptions = ((options && isObject(options.grid)) ? options.grid : {});
  1186. if (gridOptions.enabled === true) {
  1187. // Merge the user options into default grid axis options so
  1188. // that when a user option is set, it takes presedence.
  1189. gridAxisOptions = merge(true, {
  1190. className: ('highcharts-grid-axis ' + (userOptions.className || '')),
  1191. dateTimeLabelFormats: {
  1192. hour: {
  1193. list: ['%H:%M', '%H']
  1194. },
  1195. day: {
  1196. list: ['%A, %e. %B', '%a, %e. %b', '%E']
  1197. },
  1198. week: {
  1199. list: ['Week %W', 'W%W']
  1200. },
  1201. month: {
  1202. list: ['%B', '%b', '%o']
  1203. }
  1204. },
  1205. grid: {
  1206. borderWidth: 1
  1207. },
  1208. labels: {
  1209. padding: 2,
  1210. style: {
  1211. fontSize: '13px'
  1212. }
  1213. },
  1214. margin: 0,
  1215. title: {
  1216. text: null,
  1217. reserveSpace: false,
  1218. rotation: 0
  1219. },
  1220. // In a grid axis, only allow one unit of certain types,
  1221. // for example we shouln't have one grid cell spanning
  1222. // two days.
  1223. units: [[
  1224. 'millisecond',
  1225. [1, 10, 100]
  1226. ], [
  1227. 'second',
  1228. [1, 10]
  1229. ], [
  1230. 'minute',
  1231. [1, 5, 15]
  1232. ], [
  1233. 'hour',
  1234. [1, 6]
  1235. ], [
  1236. 'day',
  1237. [1]
  1238. ], [
  1239. 'week',
  1240. [1]
  1241. ], [
  1242. 'month',
  1243. [1]
  1244. ], [
  1245. 'year',
  1246. null
  1247. ]]
  1248. }, userOptions);
  1249. // X-axis specific options
  1250. if (this.coll === 'xAxis') {
  1251. // For linked axes, tickPixelInterval is used only if
  1252. // the tickPositioner below doesn't run or returns
  1253. // undefined (like multiple years)
  1254. if (defined(userOptions.linkedTo) &&
  1255. !defined(userOptions.tickPixelInterval)) {
  1256. gridAxisOptions.tickPixelInterval = 350;
  1257. }
  1258. // For the secondary grid axis, use the primary axis'
  1259. // tick intervals and return ticks one level higher.
  1260. if (
  1261. // Check for tick pixel interval in options
  1262. !defined(userOptions.tickPixelInterval) &&
  1263. // Only for linked axes
  1264. defined(userOptions.linkedTo) &&
  1265. !defined(userOptions.tickPositioner) &&
  1266. !defined(userOptions.tickInterval)) {
  1267. gridAxisOptions.tickPositioner = function (min, max) {
  1268. var parentInfo = (this.linkedParent &&
  1269. this.linkedParent.tickPositions &&
  1270. this.linkedParent.tickPositions.info);
  1271. if (parentInfo) {
  1272. var unitIdx, count, unitName, i, units = gridAxisOptions.units, unitRange;
  1273. for (i = 0; i < units.length; i++) {
  1274. if (units[i][0] ===
  1275. parentInfo.unitName) {
  1276. unitIdx = i;
  1277. break;
  1278. }
  1279. }
  1280. // Get the first allowed count on the next
  1281. // unit.
  1282. if (units[unitIdx + 1]) {
  1283. unitName = units[unitIdx + 1][0];
  1284. count =
  1285. (units[unitIdx + 1][1] || [1])[0];
  1286. // In case the base X axis shows years, make
  1287. // the secondary axis show ten times the
  1288. // years (#11427)
  1289. }
  1290. else if (parentInfo.unitName === 'year') {
  1291. unitName = 'year';
  1292. count = parentInfo.count * 10;
  1293. }
  1294. unitRange = timeUnits[unitName];
  1295. this.tickInterval = unitRange * count;
  1296. return this.getTimeTicks({
  1297. unitRange: unitRange,
  1298. count: count,
  1299. unitName: unitName
  1300. }, min, max, this.options.startOfWeek);
  1301. }
  1302. };
  1303. }
  1304. }
  1305. // Now merge the combined options into the axis options
  1306. merge(true, this.options, gridAxisOptions);
  1307. if (this.horiz) {
  1308. /* _________________________
  1309. Make this: ___|_____|_____|_____|__|
  1310. ^ ^
  1311. _________________________
  1312. Into this: |_____|_____|_____|_____|
  1313. ^ ^ */
  1314. options.minPadding = pick(userOptions.minPadding, 0);
  1315. options.maxPadding = pick(userOptions.maxPadding, 0);
  1316. }
  1317. // If borderWidth is set, then use its value for tick and
  1318. // line width.
  1319. if (isNumber(options.grid.borderWidth)) {
  1320. options.tickWidth = options.lineWidth = gridOptions.borderWidth;
  1321. }
  1322. }
  1323. };
  1324. /**
  1325. * @private
  1326. */
  1327. GridAxis.onAfterSetOptions2 = function (e) {
  1328. var axis = this;
  1329. var userOptions = e.userOptions;
  1330. var gridOptions = userOptions && userOptions.grid || {};
  1331. var columns = gridOptions.columns;
  1332. // Add column options to the parent axis. Children has their column
  1333. // options set on init in onGridAxisAfterInit.
  1334. if (gridOptions.enabled && columns) {
  1335. merge(true, axis.options, columns[columns.length - 1]);
  1336. }
  1337. };
  1338. /**
  1339. * Handle columns and setScale.
  1340. * @private
  1341. */
  1342. GridAxis.onAfterSetScale = function () {
  1343. var axis = this;
  1344. (axis.grid.columns || []).forEach(function (column) {
  1345. column.setScale();
  1346. });
  1347. };
  1348. /**
  1349. * Draw vertical axis ticks extra long to create cell floors and roofs.
  1350. * Overrides the tickLength for vertical axes.
  1351. * @private
  1352. */
  1353. GridAxis.onAfterTickSize = function (e) {
  1354. var defaultLeftAxisOptions = Axis.defaultLeftAxisOptions;
  1355. var _a = this, horiz = _a.horiz, maxLabelDimensions = _a.maxLabelDimensions, _b = _a.options.grid, gridOptions = _b === void 0 ? {} : _b;
  1356. if (gridOptions.enabled && maxLabelDimensions) {
  1357. var labelPadding = (Math.abs(defaultLeftAxisOptions.labels.x) * 2);
  1358. var distance = horiz ?
  1359. gridOptions.cellHeight || labelPadding + maxLabelDimensions.height :
  1360. labelPadding + maxLabelDimensions.width;
  1361. if (isArray(e.tickSize)) {
  1362. e.tickSize[0] = distance;
  1363. }
  1364. else {
  1365. e.tickSize = [distance, 0];
  1366. }
  1367. }
  1368. };
  1369. /**
  1370. * @private
  1371. */
  1372. GridAxis.onDestroy = function (e) {
  1373. var grid = this.grid;
  1374. (grid.columns || []).forEach(function (column) {
  1375. column.destroy(e.keepEvents);
  1376. });
  1377. grid.columns = void 0;
  1378. };
  1379. /**
  1380. * Wraps axis init to draw cell walls on vertical axes.
  1381. * @private
  1382. */
  1383. GridAxis.onInit = function (e) {
  1384. var axis = this;
  1385. var userOptions = e.userOptions || {};
  1386. var gridOptions = userOptions.grid || {};
  1387. if (gridOptions.enabled && defined(gridOptions.borderColor)) {
  1388. userOptions.tickColor = userOptions.lineColor = gridOptions.borderColor;
  1389. }
  1390. if (!axis.grid) {
  1391. axis.grid = new GridAxisAdditions(axis);
  1392. }
  1393. };
  1394. /**
  1395. * Makes tick labels which are usually ignored in a linked axis
  1396. * displayed if they are within range of linkedParent.min.
  1397. * ```
  1398. * _____________________________
  1399. * | | | | |
  1400. * Make this: | | 2 | 3 | 4 |
  1401. * |___|_______|_______|_______|
  1402. * ^
  1403. * _____________________________
  1404. * | | | | |
  1405. * Into this: | 1 | 2 | 3 | 4 |
  1406. * |___|_______|_______|_______|
  1407. * ^
  1408. * ```
  1409. * @private
  1410. * @todo Does this function do what the drawing says? Seems to affect
  1411. * ticks and not the labels directly?
  1412. */
  1413. GridAxis.onTrimTicks = function () {
  1414. var axis = this;
  1415. var options = axis.options;
  1416. var gridOptions = options.grid || {};
  1417. var categoryAxis = axis.categories;
  1418. var tickPositions = axis.tickPositions;
  1419. var firstPos = tickPositions[0];
  1420. var lastPos = tickPositions[tickPositions.length - 1];
  1421. var linkedMin = axis.linkedParent && axis.linkedParent.min;
  1422. var linkedMax = axis.linkedParent && axis.linkedParent.max;
  1423. var min = linkedMin || axis.min;
  1424. var max = linkedMax || axis.max;
  1425. var tickInterval = axis.tickInterval;
  1426. var endMoreThanMin = (firstPos < min &&
  1427. firstPos + tickInterval > min);
  1428. var startLessThanMax = (lastPos > max &&
  1429. lastPos - tickInterval < max);
  1430. if (gridOptions.enabled === true &&
  1431. !categoryAxis &&
  1432. (axis.horiz || axis.isLinked)) {
  1433. if (endMoreThanMin && !options.startOnTick) {
  1434. tickPositions[0] = min;
  1435. }
  1436. if (startLessThanMax && !options.endOnTick) {
  1437. tickPositions[tickPositions.length - 1] = max;
  1438. }
  1439. }
  1440. };
  1441. /**
  1442. * Avoid altering tickInterval when reserving space.
  1443. * @private
  1444. */
  1445. GridAxis.wrapUnsquish = function (proceed) {
  1446. var axis = this;
  1447. var _a = axis.options.grid, gridOptions = _a === void 0 ? {} : _a;
  1448. if (gridOptions.enabled === true && axis.categories) {
  1449. return axis.tickInterval;
  1450. }
  1451. return proceed.apply(axis, argsToArray(arguments));
  1452. };
  1453. return GridAxis;
  1454. }());
  1455. (function (GridAxis) {
  1456. /**
  1457. * Enum for which side the axis is on. Maps to axis.side.
  1458. * @private
  1459. */
  1460. var Side;
  1461. (function (Side) {
  1462. Side[Side["top"] = 0] = "top";
  1463. Side[Side["right"] = 1] = "right";
  1464. Side[Side["bottom"] = 2] = "bottom";
  1465. Side[Side["left"] = 3] = "left";
  1466. })(Side = GridAxis.Side || (GridAxis.Side = {}));
  1467. })(GridAxis || (GridAxis = {}));
  1468. GridAxis.compose(Axis);
  1469. return GridAxis;
  1470. });
  1471. _registerModule(_modules, 'modules/broken-axis.src.js', [_modules['parts/Axis.js'], _modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['parts/Stacking.js']], function (Axis, H, U, StackItem) {
  1472. /* *
  1473. *
  1474. * (c) 2009-2020 Torstein Honsi
  1475. *
  1476. * License: www.highcharts.com/license
  1477. *
  1478. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  1479. *
  1480. * */
  1481. var addEvent = U.addEvent, find = U.find, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, pick = U.pick;
  1482. var Series = H.Series;
  1483. /* eslint-disable valid-jsdoc */
  1484. /**
  1485. * Provides support for broken axes.
  1486. * @private
  1487. * @class
  1488. */
  1489. var BrokenAxisAdditions = /** @class */ (function () {
  1490. /* *
  1491. *
  1492. * Constructors
  1493. *
  1494. * */
  1495. function BrokenAxisAdditions(axis) {
  1496. this.hasBreaks = false;
  1497. this.axis = axis;
  1498. }
  1499. /* *
  1500. *
  1501. * Static Functions
  1502. *
  1503. * */
  1504. /**
  1505. * @private
  1506. */
  1507. BrokenAxisAdditions.isInBreak = function (brk, val) {
  1508. var ret, repeat = brk.repeat || Infinity, from = brk.from, length = brk.to - brk.from, test = (val >= from ?
  1509. (val - from) % repeat :
  1510. repeat - ((from - val) % repeat));
  1511. if (!brk.inclusive) {
  1512. ret = test < length && test !== 0;
  1513. }
  1514. else {
  1515. ret = test <= length;
  1516. }
  1517. return ret;
  1518. };
  1519. /**
  1520. * @private
  1521. */
  1522. BrokenAxisAdditions.lin2Val = function (val) {
  1523. var axis = this;
  1524. var brokenAxis = axis.brokenAxis;
  1525. var breakArray = brokenAxis && brokenAxis.breakArray;
  1526. if (!breakArray) {
  1527. return val;
  1528. }
  1529. var nval = val, brk, i;
  1530. for (i = 0; i < breakArray.length; i++) {
  1531. brk = breakArray[i];
  1532. if (brk.from >= nval) {
  1533. break;
  1534. }
  1535. else if (brk.to < nval) {
  1536. nval += brk.len;
  1537. }
  1538. else if (BrokenAxisAdditions.isInBreak(brk, nval)) {
  1539. nval += brk.len;
  1540. }
  1541. }
  1542. return nval;
  1543. };
  1544. /**
  1545. * @private
  1546. */
  1547. BrokenAxisAdditions.val2Lin = function (val) {
  1548. var axis = this;
  1549. var brokenAxis = axis.brokenAxis;
  1550. var breakArray = brokenAxis && brokenAxis.breakArray;
  1551. if (!breakArray) {
  1552. return val;
  1553. }
  1554. var nval = val, brk, i;
  1555. for (i = 0; i < breakArray.length; i++) {
  1556. brk = breakArray[i];
  1557. if (brk.to <= val) {
  1558. nval -= brk.len;
  1559. }
  1560. else if (brk.from >= val) {
  1561. break;
  1562. }
  1563. else if (BrokenAxisAdditions.isInBreak(brk, val)) {
  1564. nval -= (val - brk.from);
  1565. break;
  1566. }
  1567. }
  1568. return nval;
  1569. };
  1570. /* *
  1571. *
  1572. * Functions
  1573. *
  1574. * */
  1575. /**
  1576. * Returns the first break found where the x is larger then break.from and
  1577. * smaller then break.to.
  1578. *
  1579. * @param {number} x
  1580. * The number which should be within a break.
  1581. *
  1582. * @param {Array<Highcharts.XAxisBreaksOptions>} breaks
  1583. * The array of breaks to search within.
  1584. *
  1585. * @return {Highcharts.XAxisBreaksOptions|undefined}
  1586. * Returns the first break found that matches, returns false if no break is
  1587. * found.
  1588. */
  1589. BrokenAxisAdditions.prototype.findBreakAt = function (x, breaks) {
  1590. return find(breaks, function (b) {
  1591. return b.from < x && x < b.to;
  1592. });
  1593. };
  1594. /**
  1595. * @private
  1596. */
  1597. BrokenAxisAdditions.prototype.isInAnyBreak = function (val, testKeep) {
  1598. var brokenAxis = this;
  1599. var axis = brokenAxis.axis;
  1600. var breaks = axis.options.breaks, i = breaks && breaks.length, inbrk, keep, ret;
  1601. if (i) {
  1602. while (i--) {
  1603. if (BrokenAxisAdditions.isInBreak(breaks[i], val)) {
  1604. inbrk = true;
  1605. if (!keep) {
  1606. keep = pick(breaks[i].showPoints, !axis.isXAxis);
  1607. }
  1608. }
  1609. }
  1610. if (inbrk && testKeep) {
  1611. ret = inbrk && !keep;
  1612. }
  1613. else {
  1614. ret = inbrk;
  1615. }
  1616. }
  1617. return ret;
  1618. };
  1619. /**
  1620. * Dynamically set or unset breaks in an axis. This function in lighter than
  1621. * usin Axis.update, and it also preserves animation.
  1622. *
  1623. * @private
  1624. * @function Highcharts.Axis#setBreaks
  1625. *
  1626. * @param {Array<Highcharts.XAxisBreaksOptions>} [breaks]
  1627. * The breaks to add. When `undefined` it removes existing breaks.
  1628. *
  1629. * @param {boolean} [redraw=true]
  1630. * Whether to redraw the chart immediately.
  1631. *
  1632. * @return {void}
  1633. */
  1634. BrokenAxisAdditions.prototype.setBreaks = function (breaks, redraw) {
  1635. var brokenAxis = this;
  1636. var axis = brokenAxis.axis;
  1637. var hasBreaks = (isArray(breaks) && !!breaks.length);
  1638. axis.isDirty = brokenAxis.hasBreaks !== hasBreaks;
  1639. brokenAxis.hasBreaks = hasBreaks;
  1640. axis.options.breaks = axis.userOptions.breaks = breaks;
  1641. axis.forceRedraw = true; // Force recalculation in setScale
  1642. // Recalculate series related to the axis.
  1643. axis.series.forEach(function (series) {
  1644. series.isDirty = true;
  1645. });
  1646. if (!hasBreaks && axis.val2lin === BrokenAxisAdditions.val2Lin) {
  1647. // Revert to prototype functions
  1648. delete axis.val2lin;
  1649. delete axis.lin2val;
  1650. }
  1651. if (hasBreaks) {
  1652. axis.userOptions.ordinal = false;
  1653. axis.lin2val = BrokenAxisAdditions.lin2Val;
  1654. axis.val2lin = BrokenAxisAdditions.val2Lin;
  1655. axis.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) {
  1656. // If trying to set extremes inside a break, extend min to
  1657. // after, and max to before the break ( #3857 )
  1658. if (brokenAxis.hasBreaks) {
  1659. var axisBreak, breaks = this.options.breaks;
  1660. while ((axisBreak = brokenAxis.findBreakAt(newMin, breaks))) {
  1661. newMin = axisBreak.to;
  1662. }
  1663. while ((axisBreak = brokenAxis.findBreakAt(newMax, breaks))) {
  1664. newMax = axisBreak.from;
  1665. }
  1666. // If both min and max is within the same break.
  1667. if (newMax < newMin) {
  1668. newMax = newMin;
  1669. }
  1670. }
  1671. Axis.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments);
  1672. };
  1673. axis.setAxisTranslation = function (saveOld) {
  1674. Axis.prototype.setAxisTranslation.call(this, saveOld);
  1675. brokenAxis.unitLength = null;
  1676. if (brokenAxis.hasBreaks) {
  1677. var breaks = axis.options.breaks || [],
  1678. // Temporary one:
  1679. breakArrayT = [], breakArray = [], length = 0, inBrk, repeat, min = axis.userMin || axis.min, max = axis.userMax || axis.max, pointRangePadding = pick(axis.pointRangePadding, 0), start, i;
  1680. // Min & max check (#4247)
  1681. breaks.forEach(function (brk) {
  1682. repeat = brk.repeat || Infinity;
  1683. if (BrokenAxisAdditions.isInBreak(brk, min)) {
  1684. min +=
  1685. (brk.to % repeat) -
  1686. (min % repeat);
  1687. }
  1688. if (BrokenAxisAdditions.isInBreak(brk, max)) {
  1689. max -=
  1690. (max % repeat) -
  1691. (brk.from % repeat);
  1692. }
  1693. });
  1694. // Construct an array holding all breaks in the axis
  1695. breaks.forEach(function (brk) {
  1696. start = brk.from;
  1697. repeat = brk.repeat || Infinity;
  1698. while (start - repeat > min) {
  1699. start -= repeat;
  1700. }
  1701. while (start < min) {
  1702. start += repeat;
  1703. }
  1704. for (i = start; i < max; i += repeat) {
  1705. breakArrayT.push({
  1706. value: i,
  1707. move: 'in'
  1708. });
  1709. breakArrayT.push({
  1710. value: i + (brk.to - brk.from),
  1711. move: 'out',
  1712. size: brk.breakSize
  1713. });
  1714. }
  1715. });
  1716. breakArrayT.sort(function (a, b) {
  1717. return ((a.value === b.value) ?
  1718. ((a.move === 'in' ? 0 : 1) -
  1719. (b.move === 'in' ? 0 : 1)) :
  1720. a.value - b.value);
  1721. });
  1722. // Simplify the breaks
  1723. inBrk = 0;
  1724. start = min;
  1725. breakArrayT.forEach(function (brk) {
  1726. inBrk += (brk.move === 'in' ? 1 : -1);
  1727. if (inBrk === 1 && brk.move === 'in') {
  1728. start = brk.value;
  1729. }
  1730. if (inBrk === 0) {
  1731. breakArray.push({
  1732. from: start,
  1733. to: brk.value,
  1734. len: brk.value - start - (brk.size || 0)
  1735. });
  1736. length += brk.value - start - (brk.size || 0);
  1737. }
  1738. });
  1739. /**
  1740. * HC <= 8 backwards compatibility, used by demo samples.
  1741. * @deprecated
  1742. * @private
  1743. * @requires modules/broken-axis
  1744. */
  1745. axis.breakArray = brokenAxis.breakArray = breakArray;
  1746. // Used with staticScale, and below the actual axis length,
  1747. // when breaks are substracted.
  1748. brokenAxis.unitLength = max - min - length + pointRangePadding;
  1749. fireEvent(axis, 'afterBreaks');
  1750. if (axis.staticScale) {
  1751. axis.transA = axis.staticScale;
  1752. }
  1753. else if (brokenAxis.unitLength) {
  1754. axis.transA *=
  1755. (max - axis.min + pointRangePadding) /
  1756. brokenAxis.unitLength;
  1757. }
  1758. if (pointRangePadding) {
  1759. axis.minPixelPadding =
  1760. axis.transA * axis.minPointOffset;
  1761. }
  1762. axis.min = min;
  1763. axis.max = max;
  1764. }
  1765. };
  1766. }
  1767. if (pick(redraw, true)) {
  1768. axis.chart.redraw();
  1769. }
  1770. };
  1771. return BrokenAxisAdditions;
  1772. }());
  1773. /**
  1774. * Axis with support of broken data rows.
  1775. * @private
  1776. * @class
  1777. */
  1778. var BrokenAxis = /** @class */ (function () {
  1779. function BrokenAxis() {
  1780. }
  1781. /**
  1782. * Adds support for broken axes.
  1783. * @private
  1784. */
  1785. BrokenAxis.compose = function (AxisClass, SeriesClass) {
  1786. AxisClass.keepProps.push('brokenAxis');
  1787. var seriesProto = Series.prototype;
  1788. /**
  1789. * @private
  1790. */
  1791. seriesProto.drawBreaks = function (axis, keys) {
  1792. var series = this, points = series.points, breaks, threshold, eventName, y;
  1793. if (axis && // #5950
  1794. axis.brokenAxis &&
  1795. axis.brokenAxis.hasBreaks) {
  1796. var brokenAxis_1 = axis.brokenAxis;
  1797. keys.forEach(function (key) {
  1798. breaks = brokenAxis_1 && brokenAxis_1.breakArray || [];
  1799. threshold = axis.isXAxis ?
  1800. axis.min :
  1801. pick(series.options.threshold, axis.min);
  1802. points.forEach(function (point) {
  1803. y = pick(point['stack' + key.toUpperCase()], point[key]);
  1804. breaks.forEach(function (brk) {
  1805. if (isNumber(threshold) && isNumber(y)) {
  1806. eventName = false;
  1807. if ((threshold < brk.from && y > brk.to) ||
  1808. (threshold > brk.from && y < brk.from)) {
  1809. eventName = 'pointBreak';
  1810. }
  1811. else if ((threshold < brk.from && y > brk.from && y < brk.to) ||
  1812. (threshold > brk.from && y > brk.to && y < brk.from)) {
  1813. eventName = 'pointInBreak';
  1814. }
  1815. if (eventName) {
  1816. fireEvent(axis, eventName, { point: point, brk: brk });
  1817. }
  1818. }
  1819. });
  1820. });
  1821. });
  1822. }
  1823. };
  1824. /**
  1825. * Extend getGraphPath by identifying gaps in the data so that we can
  1826. * draw a gap in the line or area. This was moved from ordinal axis
  1827. * module to broken axis module as of #5045.
  1828. *
  1829. * @private
  1830. * @function Highcharts.Series#gappedPath
  1831. *
  1832. * @return {Highcharts.SVGPathArray}
  1833. * Gapped path
  1834. */
  1835. seriesProto.gappedPath = function () {
  1836. var currentDataGrouping = this.currentDataGrouping, groupingSize = currentDataGrouping && currentDataGrouping.gapSize, gapSize = this.options.gapSize, points = this.points.slice(), i = points.length - 1, yAxis = this.yAxis, stack;
  1837. /**
  1838. * Defines when to display a gap in the graph, together with the
  1839. * [gapUnit](plotOptions.series.gapUnit) option.
  1840. *
  1841. * In case when `dataGrouping` is enabled, points can be grouped
  1842. * into a larger time span. This can make the grouped points to have
  1843. * a greater distance than the absolute value of `gapSize` property,
  1844. * which will result in disappearing graph completely. To prevent
  1845. * this situation the mentioned distance between grouped points is
  1846. * used instead of previously defined `gapSize`.
  1847. *
  1848. * In practice, this option is most often used to visualize gaps in
  1849. * time series. In a stock chart, intraday data is available for
  1850. * daytime hours, while gaps will appear in nights and weekends.
  1851. *
  1852. * @see [gapUnit](plotOptions.series.gapUnit)
  1853. * @see [xAxis.breaks](#xAxis.breaks)
  1854. *
  1855. * @sample {highstock} stock/plotoptions/series-gapsize/
  1856. * Setting the gap size to 2 introduces gaps for weekends
  1857. * in daily datasets.
  1858. *
  1859. * @type {number}
  1860. * @default 0
  1861. * @product highstock
  1862. * @requires modules/broken-axis
  1863. * @apioption plotOptions.series.gapSize
  1864. */
  1865. /**
  1866. * Together with [gapSize](plotOptions.series.gapSize), this option
  1867. * defines where to draw gaps in the graph.
  1868. *
  1869. * When the `gapUnit` is `"relative"` (default), a gap size of 5
  1870. * means that if the distance between two points is greater than
  1871. * 5 times that of the two closest points, the graph will be broken.
  1872. *
  1873. * When the `gapUnit` is `"value"`, the gap is based on absolute
  1874. * axis values, which on a datetime axis is milliseconds. This also
  1875. * applies to the navigator series that inherits gap options from
  1876. * the base series.
  1877. *
  1878. * @see [gapSize](plotOptions.series.gapSize)
  1879. *
  1880. * @type {string}
  1881. * @default relative
  1882. * @since 5.0.13
  1883. * @product highstock
  1884. * @validvalue ["relative", "value"]
  1885. * @requires modules/broken-axis
  1886. * @apioption plotOptions.series.gapUnit
  1887. */
  1888. if (gapSize && i > 0) { // #5008
  1889. // Gap unit is relative
  1890. if (this.options.gapUnit !== 'value') {
  1891. gapSize *= this.basePointRange;
  1892. }
  1893. // Setting a new gapSize in case dataGrouping is enabled (#7686)
  1894. if (groupingSize &&
  1895. groupingSize > gapSize &&
  1896. // Except when DG is forced (e.g. from other series)
  1897. // and has lower granularity than actual points (#11351)
  1898. groupingSize >= this.basePointRange) {
  1899. gapSize = groupingSize;
  1900. }
  1901. // extension for ordinal breaks
  1902. var current = void 0, next = void 0;
  1903. while (i--) {
  1904. // Reassign next if it is not visible
  1905. if (!(next && next.visible !== false)) {
  1906. next = points[i + 1];
  1907. }
  1908. current = points[i];
  1909. // Skip iteration if one of the points is not visible
  1910. if (next.visible === false || current.visible === false) {
  1911. continue;
  1912. }
  1913. if (next.x - current.x > gapSize) {
  1914. var xRange = (current.x + next.x) / 2;
  1915. points.splice(// insert after this one
  1916. i + 1, 0, {
  1917. isNull: true,
  1918. x: xRange
  1919. });
  1920. // For stacked chart generate empty stack items, #6546
  1921. if (yAxis.stacking && this.options.stacking) {
  1922. stack = yAxis.stacking.stacks[this.stackKey][xRange] =
  1923. new StackItem(yAxis, yAxis.options
  1924. .stackLabels, false, xRange, this.stack);
  1925. stack.total = 0;
  1926. }
  1927. }
  1928. // Assign current to next for the upcoming iteration
  1929. next = current;
  1930. }
  1931. }
  1932. // Call base method
  1933. return this.getGraphPath(points);
  1934. };
  1935. /* eslint-disable no-invalid-this */
  1936. addEvent(AxisClass, 'init', function () {
  1937. var axis = this;
  1938. if (!axis.brokenAxis) {
  1939. axis.brokenAxis = new BrokenAxisAdditions(axis);
  1940. }
  1941. });
  1942. addEvent(AxisClass, 'afterInit', function () {
  1943. if (typeof this.brokenAxis !== 'undefined') {
  1944. this.brokenAxis.setBreaks(this.options.breaks, false);
  1945. }
  1946. });
  1947. addEvent(AxisClass, 'afterSetTickPositions', function () {
  1948. var axis = this;
  1949. var brokenAxis = axis.brokenAxis;
  1950. if (brokenAxis &&
  1951. brokenAxis.hasBreaks) {
  1952. var tickPositions = this.tickPositions, info = this.tickPositions.info, newPositions = [], i;
  1953. for (i = 0; i < tickPositions.length; i++) {
  1954. if (!brokenAxis.isInAnyBreak(tickPositions[i])) {
  1955. newPositions.push(tickPositions[i]);
  1956. }
  1957. }
  1958. this.tickPositions = newPositions;
  1959. this.tickPositions.info = info;
  1960. }
  1961. });
  1962. // Force Axis to be not-ordinal when breaks are defined
  1963. addEvent(AxisClass, 'afterSetOptions', function () {
  1964. if (this.brokenAxis && this.brokenAxis.hasBreaks) {
  1965. this.options.ordinal = false;
  1966. }
  1967. });
  1968. addEvent(SeriesClass, 'afterGeneratePoints', function () {
  1969. var _a = this, isDirty = _a.isDirty, connectNulls = _a.options.connectNulls, points = _a.points, xAxis = _a.xAxis, yAxis = _a.yAxis;
  1970. // Set, or reset visibility of the points. Axis.setBreaks marks the
  1971. // series as isDirty
  1972. if (isDirty) {
  1973. var i = points.length;
  1974. while (i--) {
  1975. var point = points[i];
  1976. // Respect nulls inside the break (#4275)
  1977. var nullGap = point.y === null && connectNulls === false;
  1978. var isPointInBreak = (!nullGap && ((xAxis &&
  1979. xAxis.brokenAxis &&
  1980. xAxis.brokenAxis.isInAnyBreak(point.x, true)) || (yAxis &&
  1981. yAxis.brokenAxis &&
  1982. yAxis.brokenAxis.isInAnyBreak(point.y, true))));
  1983. // Set point.visible if in any break.
  1984. // If not in break, reset visible to original value.
  1985. point.visible = isPointInBreak ?
  1986. false :
  1987. point.options.visible !== false;
  1988. }
  1989. }
  1990. });
  1991. addEvent(SeriesClass, 'afterRender', function drawPointsWrapped() {
  1992. this.drawBreaks(this.xAxis, ['x']);
  1993. this.drawBreaks(this.yAxis, pick(this.pointArrayMap, ['y']));
  1994. });
  1995. };
  1996. return BrokenAxis;
  1997. }());
  1998. BrokenAxis.compose(Axis, Series); // @todo remove automatism
  1999. return BrokenAxis;
  2000. });
  2001. _registerModule(_modules, 'parts-gantt/TreeGridAxis.js', [_modules['parts/Axis.js'], _modules['parts/Tick.js'], _modules['parts-gantt/Tree.js'], _modules['parts-gantt/TreeGridTick.js'], _modules['mixins/tree-series.js'], _modules['parts/Utilities.js']], function (Axis, Tick, Tree, TreeGridTick, TreeSeriesMixin, U) {
  2002. /* *
  2003. *
  2004. * (c) 2016 Highsoft AS
  2005. * Authors: Jon Arild Nygard
  2006. *
  2007. * License: www.highcharts.com/license
  2008. *
  2009. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  2010. *
  2011. * */
  2012. var addEvent = U.addEvent, find = U.find, fireEvent = U.fireEvent, isNumber = U.isNumber, isObject = U.isObject, isString = U.isString, merge = U.merge, pick = U.pick, wrap = U.wrap;
  2013. /**
  2014. * @private
  2015. */
  2016. var TreeGridAxis;
  2017. (function (TreeGridAxis) {
  2018. /* *
  2019. *
  2020. * Interfaces
  2021. *
  2022. * */
  2023. /* *
  2024. *
  2025. * Variables
  2026. *
  2027. * */
  2028. var applied = false;
  2029. /* *
  2030. *
  2031. * Functions
  2032. *
  2033. * */
  2034. /**
  2035. * @private
  2036. */
  2037. function compose(AxisClass) {
  2038. if (!applied) {
  2039. wrap(AxisClass.prototype, 'generateTick', wrapGenerateTick);
  2040. wrap(AxisClass.prototype, 'getMaxLabelDimensions', wrapGetMaxLabelDimensions);
  2041. wrap(AxisClass.prototype, 'init', wrapInit);
  2042. wrap(AxisClass.prototype, 'setTickInterval', wrapSetTickInterval);
  2043. TreeGridTick.compose(Tick);
  2044. applied = true;
  2045. }
  2046. }
  2047. TreeGridAxis.compose = compose;
  2048. /**
  2049. * @private
  2050. */
  2051. function getBreakFromNode(node, max) {
  2052. var from = node.collapseStart || 0, to = node.collapseEnd || 0;
  2053. // In broken-axis, the axis.max is minimized until it is not within a
  2054. // break. Therefore, if break.to is larger than axis.max, the axis.to
  2055. // should not add the 0.5 axis.tickMarkOffset, to avoid adding a break
  2056. // larger than axis.max.
  2057. // TODO consider simplifying broken-axis and this might solve itself
  2058. if (to >= max) {
  2059. from -= 0.5;
  2060. }
  2061. return {
  2062. from: from,
  2063. to: to,
  2064. showPoints: false
  2065. };
  2066. }
  2067. /**
  2068. * Creates a tree structure of the data, and the treegrid. Calculates
  2069. * categories, and y-values of points based on the tree.
  2070. *
  2071. * @private
  2072. * @function getTreeGridFromData
  2073. *
  2074. * @param {Array<Highcharts.GanttPointOptions>} data
  2075. * All the data points to display in the axis.
  2076. *
  2077. * @param {boolean} uniqueNames
  2078. * Wether or not the data node with the same name should share grid cell. If
  2079. * true they do share cell. False by default.
  2080. *
  2081. * @param {number} numberOfSeries
  2082. *
  2083. * @return {object}
  2084. * Returns an object containing categories, mapOfIdToNode,
  2085. * mapOfPosToGridNode, and tree.
  2086. *
  2087. * @todo There should be only one point per line.
  2088. * @todo It should be optional to have one category per point, or merge
  2089. * cells
  2090. * @todo Add unit-tests.
  2091. */
  2092. function getTreeGridFromData(data, uniqueNames, numberOfSeries) {
  2093. var categories = [], collapsedNodes = [], mapOfIdToNode = {}, mapOfPosToGridNode = {}, posIterator = -1, uniqueNamesEnabled = typeof uniqueNames === 'boolean' ? uniqueNames : false, tree;
  2094. // Build the tree from the series data.
  2095. var treeParams = {
  2096. // After the children has been created.
  2097. after: function (node) {
  2098. var gridNode = mapOfPosToGridNode[node.pos], height = 0, descendants = 0;
  2099. gridNode.children.forEach(function (child) {
  2100. descendants += (child.descendants || 0) + 1;
  2101. height = Math.max((child.height || 0) + 1, height);
  2102. });
  2103. gridNode.descendants = descendants;
  2104. gridNode.height = height;
  2105. if (gridNode.collapsed) {
  2106. collapsedNodes.push(gridNode);
  2107. }
  2108. },
  2109. // Before the children has been created.
  2110. before: function (node) {
  2111. var data = isObject(node.data, true) ? node.data : {}, name = isString(data.name) ? data.name : '', parentNode = mapOfIdToNode[node.parent], parentGridNode = (isObject(parentNode, true) ?
  2112. mapOfPosToGridNode[parentNode.pos] :
  2113. null), hasSameName = function (x) {
  2114. return x.name === name;
  2115. }, gridNode, pos;
  2116. // If not unique names, look for sibling node with the same name
  2117. if (uniqueNamesEnabled &&
  2118. isObject(parentGridNode, true) &&
  2119. !!(gridNode = find(parentGridNode.children, hasSameName))) {
  2120. // If there is a gridNode with the same name, reuse position
  2121. pos = gridNode.pos;
  2122. // Add data node to list of nodes in the grid node.
  2123. gridNode.nodes.push(node);
  2124. }
  2125. else {
  2126. // If it is a new grid node, increment position.
  2127. pos = posIterator++;
  2128. }
  2129. // Add new grid node to map.
  2130. if (!mapOfPosToGridNode[pos]) {
  2131. mapOfPosToGridNode[pos] = gridNode = {
  2132. depth: parentGridNode ? parentGridNode.depth + 1 : 0,
  2133. name: name,
  2134. nodes: [node],
  2135. children: [],
  2136. pos: pos
  2137. };
  2138. // If not root, then add name to categories.
  2139. if (pos !== -1) {
  2140. categories.push(name);
  2141. }
  2142. // Add name to list of children.
  2143. if (isObject(parentGridNode, true)) {
  2144. parentGridNode.children.push(gridNode);
  2145. }
  2146. }
  2147. // Add data node to map
  2148. if (isString(node.id)) {
  2149. mapOfIdToNode[node.id] = node;
  2150. }
  2151. // If one of the points are collapsed, then start the grid node
  2152. // in collapsed state.
  2153. if (gridNode &&
  2154. data.collapsed === true) {
  2155. gridNode.collapsed = true;
  2156. }
  2157. // Assign pos to data node
  2158. node.pos = pos;
  2159. }
  2160. };
  2161. var updateYValuesAndTickPos = function (map, numberOfSeries) {
  2162. var setValues = function (gridNode, start, result) {
  2163. var nodes = gridNode.nodes, end = start + (start === -1 ? 0 : numberOfSeries - 1), diff = (end - start) / 2, padding = 0.5, pos = start + diff;
  2164. nodes.forEach(function (node) {
  2165. var data = node.data;
  2166. if (isObject(data, true)) {
  2167. // Update point
  2168. data.y = start + (data.seriesIndex || 0);
  2169. // Remove the property once used
  2170. delete data.seriesIndex;
  2171. }
  2172. node.pos = pos;
  2173. });
  2174. result[pos] = gridNode;
  2175. gridNode.pos = pos;
  2176. gridNode.tickmarkOffset = diff + padding;
  2177. gridNode.collapseStart = end + padding;
  2178. gridNode.children.forEach(function (child) {
  2179. setValues(child, end + 1, result);
  2180. end = (child.collapseEnd || 0) - padding;
  2181. });
  2182. // Set collapseEnd to the end of the last child node.
  2183. gridNode.collapseEnd = end + padding;
  2184. return result;
  2185. };
  2186. return setValues(map['-1'], -1, {});
  2187. };
  2188. // Create tree from data
  2189. tree = Tree.getTree(data, treeParams);
  2190. // Update y values of data, and set calculate tick positions.
  2191. mapOfPosToGridNode = updateYValuesAndTickPos(mapOfPosToGridNode, numberOfSeries);
  2192. // Return the resulting data.
  2193. return {
  2194. categories: categories,
  2195. mapOfIdToNode: mapOfIdToNode,
  2196. mapOfPosToGridNode: mapOfPosToGridNode,
  2197. collapsedNodes: collapsedNodes,
  2198. tree: tree
  2199. };
  2200. }
  2201. /**
  2202. * Builds the tree of categories and calculates its positions.
  2203. * @private
  2204. * @param {object} e Event object
  2205. * @param {object} e.target The chart instance which the event was fired on.
  2206. * @param {object[]} e.target.axes The axes of the chart.
  2207. */
  2208. function onBeforeRender(e) {
  2209. var chart = e.target, axes = chart.axes;
  2210. axes.filter(function (axis) {
  2211. return axis.options.type === 'treegrid';
  2212. }).forEach(function (axis) {
  2213. var options = axis.options || {}, labelOptions = options.labels, uniqueNames = options.uniqueNames, numberOfSeries = 0, isDirty, data, treeGrid;
  2214. // Check whether any of series is rendering for the first time,
  2215. // visibility has changed, or its data is dirty,
  2216. // and only then update. #10570, #10580
  2217. // Also check if mapOfPosToGridNode exists. #10887
  2218. isDirty = (!axis.treeGrid.mapOfPosToGridNode ||
  2219. axis.series.some(function (series) {
  2220. return !series.hasRendered ||
  2221. series.isDirtyData ||
  2222. series.isDirty;
  2223. }));
  2224. if (isDirty) {
  2225. // Concatenate data from all series assigned to this axis.
  2226. data = axis.series.reduce(function (arr, s) {
  2227. if (s.visible) {
  2228. // Push all data to array
  2229. (s.options.data || []).forEach(function (data) {
  2230. if (isObject(data, true)) {
  2231. // Set series index on data. Removed again
  2232. // after use.
  2233. data.seriesIndex = numberOfSeries;
  2234. arr.push(data);
  2235. }
  2236. });
  2237. // Increment series index
  2238. if (uniqueNames === true) {
  2239. numberOfSeries++;
  2240. }
  2241. }
  2242. return arr;
  2243. }, []);
  2244. // setScale is fired after all the series is initialized,
  2245. // which is an ideal time to update the axis.categories.
  2246. treeGrid = getTreeGridFromData(data, uniqueNames || false, (uniqueNames === true) ? numberOfSeries : 1);
  2247. // Assign values to the axis.
  2248. axis.categories = treeGrid.categories;
  2249. axis.treeGrid.mapOfPosToGridNode = treeGrid.mapOfPosToGridNode;
  2250. axis.hasNames = true;
  2251. axis.treeGrid.tree = treeGrid.tree;
  2252. // Update yData now that we have calculated the y values
  2253. axis.series.forEach(function (series) {
  2254. var data = (series.options.data || []).map(function (d) {
  2255. return isObject(d, true) ? merge(d) : d;
  2256. });
  2257. // Avoid destroying points when series is not visible
  2258. if (series.visible) {
  2259. series.setData(data, false);
  2260. }
  2261. });
  2262. // Calculate the label options for each level in the tree.
  2263. axis.treeGrid.mapOptionsToLevel =
  2264. TreeSeriesMixin.getLevelOptions({
  2265. defaults: labelOptions,
  2266. from: 1,
  2267. levels: labelOptions && labelOptions.levels,
  2268. to: axis.treeGrid.tree && axis.treeGrid.tree.height
  2269. });
  2270. // Setting initial collapsed nodes
  2271. if (e.type === 'beforeRender') {
  2272. axis.treeGrid.collapsedNodes = treeGrid.collapsedNodes;
  2273. }
  2274. }
  2275. });
  2276. }
  2277. /**
  2278. * Generates a tick for initial positioning.
  2279. *
  2280. * @private
  2281. * @function Highcharts.GridAxis#generateTick
  2282. *
  2283. * @param {Function} proceed
  2284. * The original generateTick function.
  2285. *
  2286. * @param {number} pos
  2287. * The tick position in axis values.
  2288. */
  2289. function wrapGenerateTick(proceed, pos) {
  2290. var axis = this, mapOptionsToLevel = axis.treeGrid.mapOptionsToLevel || {}, isTreeGrid = axis.options.type === 'treegrid', ticks = axis.ticks;
  2291. var tick = ticks[pos], levelOptions, options, gridNode;
  2292. if (isTreeGrid &&
  2293. axis.treeGrid.mapOfPosToGridNode) {
  2294. gridNode = axis.treeGrid.mapOfPosToGridNode[pos];
  2295. levelOptions = mapOptionsToLevel[gridNode.depth];
  2296. if (levelOptions) {
  2297. options = {
  2298. labels: levelOptions
  2299. };
  2300. }
  2301. if (!tick) {
  2302. ticks[pos] = tick =
  2303. new Tick(axis, pos, void 0, void 0, {
  2304. category: gridNode.name,
  2305. tickmarkOffset: gridNode.tickmarkOffset,
  2306. options: options
  2307. });
  2308. }
  2309. else {
  2310. // update labels depending on tick interval
  2311. tick.parameters.category = gridNode.name;
  2312. tick.options = options;
  2313. tick.addLabel();
  2314. }
  2315. }
  2316. else {
  2317. proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
  2318. }
  2319. }
  2320. /**
  2321. * Override to add indentation to axis.maxLabelDimensions.
  2322. *
  2323. * @private
  2324. * @function Highcharts.GridAxis#getMaxLabelDimensions
  2325. *
  2326. * @param {Function} proceed
  2327. * The original function
  2328. */
  2329. function wrapGetMaxLabelDimensions(proceed) {
  2330. var axis = this, options = axis.options, labelOptions = options && options.labels, indentation = (labelOptions && isNumber(labelOptions.indentation) ?
  2331. labelOptions.indentation :
  2332. 0), retVal = proceed.apply(axis, Array.prototype.slice.call(arguments, 1)), isTreeGrid = axis.options.type === 'treegrid';
  2333. var treeDepth;
  2334. if (isTreeGrid && axis.treeGrid.mapOfPosToGridNode) {
  2335. treeDepth = axis.treeGrid.mapOfPosToGridNode[-1].height || 0;
  2336. retVal.width += indentation * (treeDepth - 1);
  2337. }
  2338. return retVal;
  2339. }
  2340. /**
  2341. * @private
  2342. */
  2343. function wrapInit(proceed, chart, userOptions) {
  2344. var axis = this, isTreeGrid = userOptions.type === 'treegrid';
  2345. if (!axis.treeGrid) {
  2346. axis.treeGrid = new Additions(axis);
  2347. }
  2348. // Set default and forced options for TreeGrid
  2349. if (isTreeGrid) {
  2350. // Add event for updating the categories of a treegrid.
  2351. // NOTE Preferably these events should be set on the axis.
  2352. addEvent(chart, 'beforeRender', onBeforeRender);
  2353. addEvent(chart, 'beforeRedraw', onBeforeRender);
  2354. // Add new collapsed nodes on addseries
  2355. addEvent(chart, 'addSeries', function (e) {
  2356. if (e.options.data) {
  2357. var treeGrid = getTreeGridFromData(e.options.data, userOptions.uniqueNames || false, 1);
  2358. axis.treeGrid.collapsedNodes = (axis.treeGrid.collapsedNodes || []).concat(treeGrid.collapsedNodes);
  2359. }
  2360. });
  2361. // Collapse all nodes in axis.treegrid.collapsednodes
  2362. // where collapsed equals true.
  2363. addEvent(axis, 'foundExtremes', function () {
  2364. if (axis.treeGrid.collapsedNodes) {
  2365. axis.treeGrid.collapsedNodes.forEach(function (node) {
  2366. var breaks = axis.treeGrid.collapse(node);
  2367. if (axis.brokenAxis) {
  2368. axis.brokenAxis.setBreaks(breaks, false);
  2369. // remove the node from the axis collapsedNodes
  2370. if (axis.treeGrid.collapsedNodes) {
  2371. axis.treeGrid.collapsedNodes = axis.treeGrid.collapsedNodes.filter(function (n) {
  2372. return node.collapseStart !== n.collapseStart ||
  2373. node.collapseEnd !== n.collapseEnd;
  2374. });
  2375. }
  2376. }
  2377. });
  2378. }
  2379. });
  2380. // If staticScale is not defined on the yAxis
  2381. // and chart height is set, set axis.isDirty
  2382. // to ensure collapsing works (#12012)
  2383. addEvent(axis, 'afterBreaks', function () {
  2384. var _a;
  2385. if (axis.coll === 'yAxis' && !axis.staticScale && ((_a = axis.chart.options.chart) === null || _a === void 0 ? void 0 : _a.height)) {
  2386. axis.isDirty = true;
  2387. }
  2388. });
  2389. userOptions = merge({
  2390. // Default options
  2391. grid: {
  2392. enabled: true
  2393. },
  2394. // TODO: add support for align in treegrid.
  2395. labels: {
  2396. align: 'left',
  2397. /**
  2398. * Set options on specific levels in a tree grid axis. Takes
  2399. * precedence over labels options.
  2400. *
  2401. * @sample {gantt} gantt/treegrid-axis/labels-levels
  2402. * Levels on TreeGrid Labels
  2403. *
  2404. * @type {Array<*>}
  2405. * @product gantt
  2406. * @apioption yAxis.labels.levels
  2407. *
  2408. * @private
  2409. */
  2410. levels: [{
  2411. /**
  2412. * Specify the level which the options within this object
  2413. * applies to.
  2414. *
  2415. * @type {number}
  2416. * @product gantt
  2417. * @apioption yAxis.labels.levels.level
  2418. *
  2419. * @private
  2420. */
  2421. level: void 0
  2422. }, {
  2423. level: 1,
  2424. /**
  2425. * @type {Highcharts.CSSObject}
  2426. * @product gantt
  2427. * @apioption yAxis.labels.levels.style
  2428. *
  2429. * @private
  2430. */
  2431. style: {
  2432. /** @ignore-option */
  2433. fontWeight: 'bold'
  2434. }
  2435. }],
  2436. /**
  2437. * The symbol for the collapse and expand icon in a
  2438. * treegrid.
  2439. *
  2440. * @product gantt
  2441. * @optionparent yAxis.labels.symbol
  2442. *
  2443. * @private
  2444. */
  2445. symbol: {
  2446. /**
  2447. * The symbol type. Points to a definition function in
  2448. * the `Highcharts.Renderer.symbols` collection.
  2449. *
  2450. * @type {Highcharts.SymbolKeyValue}
  2451. *
  2452. * @private
  2453. */
  2454. type: 'triangle',
  2455. x: -5,
  2456. y: -5,
  2457. height: 10,
  2458. width: 10,
  2459. padding: 5
  2460. }
  2461. },
  2462. uniqueNames: false
  2463. }, userOptions, {
  2464. // Forced options
  2465. reversed: true,
  2466. // grid.columns is not supported in treegrid
  2467. grid: {
  2468. columns: void 0
  2469. }
  2470. });
  2471. }
  2472. // Now apply the original function with the original arguments,
  2473. // which are sliced off this function's arguments
  2474. proceed.apply(axis, [chart, userOptions]);
  2475. if (isTreeGrid) {
  2476. axis.hasNames = true;
  2477. axis.options.showLastLabel = true;
  2478. }
  2479. }
  2480. /**
  2481. * Set the tick positions, tickInterval, axis min and max.
  2482. *
  2483. * @private
  2484. * @function Highcharts.GridAxis#setTickInterval
  2485. *
  2486. * @param {Function} proceed
  2487. * The original setTickInterval function.
  2488. */
  2489. function wrapSetTickInterval(proceed) {
  2490. var axis = this, options = axis.options, isTreeGrid = options.type === 'treegrid';
  2491. if (isTreeGrid) {
  2492. axis.min = pick(axis.userMin, options.min, axis.dataMin);
  2493. axis.max = pick(axis.userMax, options.max, axis.dataMax);
  2494. fireEvent(axis, 'foundExtremes');
  2495. // setAxisTranslation modifies the min and max according to
  2496. // axis breaks.
  2497. axis.setAxisTranslation(true);
  2498. axis.tickmarkOffset = 0.5;
  2499. axis.tickInterval = 1;
  2500. axis.tickPositions = axis.treeGrid.mapOfPosToGridNode ?
  2501. axis.treeGrid.getTickPositions() :
  2502. [];
  2503. }
  2504. else {
  2505. proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
  2506. }
  2507. }
  2508. /* *
  2509. *
  2510. * Classes
  2511. *
  2512. * */
  2513. /**
  2514. * @private
  2515. * @class
  2516. */
  2517. var Additions = /** @class */ (function () {
  2518. /* *
  2519. *
  2520. * Constructors
  2521. *
  2522. * */
  2523. /**
  2524. * @private
  2525. */
  2526. function Additions(axis) {
  2527. this.axis = axis;
  2528. }
  2529. /* *
  2530. *
  2531. * Functions
  2532. *
  2533. * */
  2534. /**
  2535. * Calculates the new axis breaks to collapse a node.
  2536. *
  2537. * @private
  2538. *
  2539. * @param {Highcharts.Axis} axis
  2540. * The axis to check against.
  2541. *
  2542. * @param {Highcharts.GridNode} node
  2543. * The node to collapse.
  2544. *
  2545. * @param {number} pos
  2546. * The tick position to collapse.
  2547. *
  2548. * @return {Array<object>}
  2549. * Returns an array of the new breaks for the axis.
  2550. */
  2551. Additions.prototype.collapse = function (node) {
  2552. var axis = this.axis, breaks = (axis.options.breaks || []), obj = getBreakFromNode(node, axis.max);
  2553. breaks.push(obj);
  2554. return breaks;
  2555. };
  2556. /**
  2557. * Calculates the new axis breaks to expand a node.
  2558. *
  2559. * @private
  2560. *
  2561. * @param {Highcharts.Axis} axis
  2562. * The axis to check against.
  2563. *
  2564. * @param {Highcharts.GridNode} node
  2565. * The node to expand.
  2566. *
  2567. * @param {number} pos
  2568. * The tick position to expand.
  2569. *
  2570. * @return {Array<object>}
  2571. * Returns an array of the new breaks for the axis.
  2572. */
  2573. Additions.prototype.expand = function (node) {
  2574. var axis = this.axis, breaks = (axis.options.breaks || []), obj = getBreakFromNode(node, axis.max);
  2575. // Remove the break from the axis breaks array.
  2576. return breaks.reduce(function (arr, b) {
  2577. if (b.to !== obj.to || b.from !== obj.from) {
  2578. arr.push(b);
  2579. }
  2580. return arr;
  2581. }, []);
  2582. };
  2583. /**
  2584. * Creates a list of positions for the ticks on the axis. Filters out
  2585. * positions that are outside min and max, or is inside an axis break.
  2586. *
  2587. * @private
  2588. *
  2589. * @return {Array<number>}
  2590. * List of positions.
  2591. */
  2592. Additions.prototype.getTickPositions = function () {
  2593. var axis = this.axis;
  2594. return Object.keys(axis.treeGrid.mapOfPosToGridNode || {}).reduce(function (arr, key) {
  2595. var pos = +key;
  2596. if (axis.min <= pos &&
  2597. axis.max >= pos &&
  2598. !(axis.brokenAxis && axis.brokenAxis.isInAnyBreak(pos))) {
  2599. arr.push(pos);
  2600. }
  2601. return arr;
  2602. }, []);
  2603. };
  2604. /**
  2605. * Check if a node is collapsed.
  2606. *
  2607. * @private
  2608. *
  2609. * @param {Highcharts.Axis} axis
  2610. * The axis to check against.
  2611. *
  2612. * @param {object} node
  2613. * The node to check if is collapsed.
  2614. *
  2615. * @param {number} pos
  2616. * The tick position to collapse.
  2617. *
  2618. * @return {boolean}
  2619. * Returns true if collapsed, false if expanded.
  2620. */
  2621. Additions.prototype.isCollapsed = function (node) {
  2622. var axis = this.axis, breaks = (axis.options.breaks || []), obj = getBreakFromNode(node, axis.max);
  2623. return breaks.some(function (b) {
  2624. return b.from === obj.from && b.to === obj.to;
  2625. });
  2626. };
  2627. /**
  2628. * Calculates the new axis breaks after toggling the collapse/expand
  2629. * state of a node. If it is collapsed it will be expanded, and if it is
  2630. * exapended it will be collapsed.
  2631. *
  2632. * @private
  2633. *
  2634. * @param {Highcharts.Axis} axis
  2635. * The axis to check against.
  2636. *
  2637. * @param {Highcharts.GridNode} node
  2638. * The node to toggle.
  2639. *
  2640. * @return {Array<object>}
  2641. * Returns an array of the new breaks for the axis.
  2642. */
  2643. Additions.prototype.toggleCollapse = function (node) {
  2644. return (this.isCollapsed(node) ?
  2645. this.expand(node) :
  2646. this.collapse(node));
  2647. };
  2648. return Additions;
  2649. }());
  2650. TreeGridAxis.Additions = Additions;
  2651. })(TreeGridAxis || (TreeGridAxis = {}));
  2652. // Make utility functions available for testing.
  2653. Axis.prototype.utils = {
  2654. getNode: Tree.getNode
  2655. };
  2656. TreeGridAxis.compose(Axis);
  2657. return TreeGridAxis;
  2658. });
  2659. _registerModule(_modules, 'parts-gantt/CurrentDateIndicator.js', [_modules['parts/Globals.js'], _modules['parts/Options.js'], _modules['parts/Utilities.js'], _modules['parts/PlotLineOrBand.js']], function (H, O, U, PlotLineOrBand) {
  2660. /* *
  2661. *
  2662. * (c) 2016-2020 Highsoft AS
  2663. *
  2664. * Author: Lars A. V. Cabrera
  2665. *
  2666. * License: www.highcharts.com/license
  2667. *
  2668. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  2669. *
  2670. * */
  2671. var dateFormat = O.dateFormat;
  2672. var addEvent = U.addEvent, merge = U.merge, wrap = U.wrap;
  2673. var Axis = H.Axis;
  2674. var defaultConfig = {
  2675. /**
  2676. * Show an indicator on the axis for the current date and time. Can be a
  2677. * boolean or a configuration object similar to
  2678. * [xAxis.plotLines](#xAxis.plotLines).
  2679. *
  2680. * @sample gantt/current-date-indicator/demo
  2681. * Current date indicator enabled
  2682. * @sample gantt/current-date-indicator/object-config
  2683. * Current date indicator with custom options
  2684. *
  2685. * @declare Highcharts.AxisCurrentDateIndicatorOptions
  2686. * @type {boolean|*}
  2687. * @default true
  2688. * @extends xAxis.plotLines
  2689. * @excluding value
  2690. * @product gantt
  2691. * @apioption xAxis.currentDateIndicator
  2692. */
  2693. currentDateIndicator: true,
  2694. color: '#ccd6eb',
  2695. width: 2,
  2696. /**
  2697. * @declare Highcharts.AxisCurrentDateIndicatorLabelOptions
  2698. */
  2699. label: {
  2700. /**
  2701. * Format of the label. This options is passed as the fist argument to
  2702. * [dateFormat](/class-reference/Highcharts#dateFormat) function.
  2703. *
  2704. * @type {string}
  2705. * @default '%a, %b %d %Y, %H:%M'
  2706. * @product gantt
  2707. * @apioption xAxis.currentDateIndicator.label.format
  2708. */
  2709. format: '%a, %b %d %Y, %H:%M',
  2710. formatter: function (value, format) {
  2711. return dateFormat(format, value);
  2712. },
  2713. rotation: 0,
  2714. /**
  2715. * @type {Highcharts.CSSObject}
  2716. */
  2717. style: {
  2718. /** @internal */
  2719. fontSize: '10px'
  2720. }
  2721. }
  2722. };
  2723. /* eslint-disable no-invalid-this */
  2724. addEvent(Axis, 'afterSetOptions', function () {
  2725. var options = this.options, cdiOptions = options.currentDateIndicator;
  2726. if (cdiOptions) {
  2727. cdiOptions = typeof cdiOptions === 'object' ?
  2728. merge(defaultConfig, cdiOptions) : merge(defaultConfig);
  2729. cdiOptions.value = new Date();
  2730. if (!options.plotLines) {
  2731. options.plotLines = [];
  2732. }
  2733. options.plotLines.push(cdiOptions);
  2734. }
  2735. });
  2736. addEvent(PlotLineOrBand, 'render', function () {
  2737. // If the label already exists, update its text
  2738. if (this.label) {
  2739. this.label.attr({
  2740. text: this.getLabelText(this.options.label)
  2741. });
  2742. }
  2743. });
  2744. wrap(PlotLineOrBand.prototype, 'getLabelText', function (defaultMethod, defaultLabelOptions) {
  2745. var options = this.options;
  2746. if (options.currentDateIndicator && options.label &&
  2747. typeof options.label.formatter === 'function') {
  2748. options.value = new Date();
  2749. return options.label.formatter
  2750. .call(this, options.value, options.label.format);
  2751. }
  2752. return defaultMethod.call(this, defaultLabelOptions);
  2753. });
  2754. });
  2755. _registerModule(_modules, 'modules/static-scale.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
  2756. /* *
  2757. *
  2758. * (c) 2016-2020 Torstein Honsi, Lars Cabrera
  2759. *
  2760. * License: www.highcharts.com/license
  2761. *
  2762. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  2763. *
  2764. * */
  2765. var addEvent = U.addEvent, defined = U.defined, isNumber = U.isNumber, pick = U.pick;
  2766. var Chart = H.Chart;
  2767. /* eslint-disable no-invalid-this */
  2768. /**
  2769. * For vertical axes only. Setting the static scale ensures that each tick unit
  2770. * is translated into a fixed pixel height. For example, setting the static
  2771. * scale to 24 results in each Y axis category taking up 24 pixels, and the
  2772. * height of the chart adjusts. Adding or removing items will make the chart
  2773. * resize.
  2774. *
  2775. * @sample gantt/xrange-series/demo/
  2776. * X-range series with static scale
  2777. *
  2778. * @type {number}
  2779. * @default 50
  2780. * @since 6.2.0
  2781. * @product gantt
  2782. * @apioption yAxis.staticScale
  2783. */
  2784. addEvent(H.Axis, 'afterSetOptions', function () {
  2785. var chartOptions = this.chart.options && this.chart.options.chart;
  2786. if (!this.horiz &&
  2787. isNumber(this.options.staticScale) &&
  2788. (!chartOptions.height ||
  2789. (chartOptions.scrollablePlotArea &&
  2790. chartOptions.scrollablePlotArea.minHeight))) {
  2791. this.staticScale = this.options.staticScale;
  2792. }
  2793. });
  2794. Chart.prototype.adjustHeight = function () {
  2795. if (this.redrawTrigger !== 'adjustHeight') {
  2796. (this.axes || []).forEach(function (axis) {
  2797. var chart = axis.chart, animate = !!chart.initiatedScale &&
  2798. chart.options.animation, staticScale = axis.options.staticScale, height, diff;
  2799. if (axis.staticScale && defined(axis.min)) {
  2800. height = pick(axis.brokenAxis && axis.brokenAxis.unitLength, axis.max + axis.tickInterval - axis.min) * staticScale;
  2801. // Minimum height is 1 x staticScale.
  2802. height = Math.max(height, staticScale);
  2803. diff = height - chart.plotHeight;
  2804. if (Math.abs(diff) >= 1) {
  2805. chart.plotHeight = height;
  2806. chart.redrawTrigger = 'adjustHeight';
  2807. chart.setSize(void 0, chart.chartHeight + diff, animate);
  2808. }
  2809. // Make sure clip rects have the right height before initial
  2810. // animation.
  2811. axis.series.forEach(function (series) {
  2812. var clipRect = series.sharedClipKey &&
  2813. chart[series.sharedClipKey];
  2814. if (clipRect) {
  2815. clipRect.attr({
  2816. height: chart.plotHeight
  2817. });
  2818. }
  2819. });
  2820. }
  2821. });
  2822. this.initiatedScale = true;
  2823. }
  2824. this.redrawTrigger = null;
  2825. };
  2826. addEvent(Chart, 'render', Chart.prototype.adjustHeight);
  2827. });
  2828. _registerModule(_modules, 'parts-gantt/PathfinderAlgorithms.js', [_modules['parts/Utilities.js']], function (U) {
  2829. /* *
  2830. *
  2831. * (c) 2016 Highsoft AS
  2832. * Author: Øystein Moseng
  2833. *
  2834. * License: www.highcharts.com/license
  2835. *
  2836. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  2837. *
  2838. * */
  2839. var extend = U.extend, pick = U.pick;
  2840. var min = Math.min, max = Math.max, abs = Math.abs;
  2841. /**
  2842. * Get index of last obstacle before xMin. Employs a type of binary search, and
  2843. * thus requires that obstacles are sorted by xMin value.
  2844. *
  2845. * @private
  2846. * @function findLastObstacleBefore
  2847. *
  2848. * @param {Array<object>} obstacles
  2849. * Array of obstacles to search in.
  2850. *
  2851. * @param {number} xMin
  2852. * The xMin threshold.
  2853. *
  2854. * @param {number} [startIx]
  2855. * Starting index to search from. Must be within array range.
  2856. *
  2857. * @return {number}
  2858. * The index of the last obstacle element before xMin.
  2859. */
  2860. function findLastObstacleBefore(obstacles, xMin, startIx) {
  2861. var left = startIx || 0, // left limit
  2862. right = obstacles.length - 1, // right limit
  2863. min = xMin - 0.0000001, // Make sure we include all obstacles at xMin
  2864. cursor, cmp;
  2865. while (left <= right) {
  2866. cursor = (right + left) >> 1;
  2867. cmp = min - obstacles[cursor].xMin;
  2868. if (cmp > 0) {
  2869. left = cursor + 1;
  2870. }
  2871. else if (cmp < 0) {
  2872. right = cursor - 1;
  2873. }
  2874. else {
  2875. return cursor;
  2876. }
  2877. }
  2878. return left > 0 ? left - 1 : 0;
  2879. }
  2880. /**
  2881. * Test if a point lays within an obstacle.
  2882. *
  2883. * @private
  2884. * @function pointWithinObstacle
  2885. *
  2886. * @param {object} obstacle
  2887. * Obstacle to test.
  2888. *
  2889. * @param {Highcharts.Point} point
  2890. * Point with x/y props.
  2891. *
  2892. * @return {boolean}
  2893. * Whether point is within the obstacle or not.
  2894. */
  2895. function pointWithinObstacle(obstacle, point) {
  2896. return (point.x <= obstacle.xMax &&
  2897. point.x >= obstacle.xMin &&
  2898. point.y <= obstacle.yMax &&
  2899. point.y >= obstacle.yMin);
  2900. }
  2901. /**
  2902. * Find the index of an obstacle that wraps around a point.
  2903. * Returns -1 if not found.
  2904. *
  2905. * @private
  2906. * @function findObstacleFromPoint
  2907. *
  2908. * @param {Array<object>} obstacles
  2909. * Obstacles to test.
  2910. *
  2911. * @param {Highcharts.Point} point
  2912. * Point with x/y props.
  2913. *
  2914. * @return {number}
  2915. * Ix of the obstacle in the array, or -1 if not found.
  2916. */
  2917. function findObstacleFromPoint(obstacles, point) {
  2918. var i = findLastObstacleBefore(obstacles, point.x + 1) + 1;
  2919. while (i--) {
  2920. if (obstacles[i].xMax >= point.x &&
  2921. // optimization using lazy evaluation
  2922. pointWithinObstacle(obstacles[i], point)) {
  2923. return i;
  2924. }
  2925. }
  2926. return -1;
  2927. }
  2928. /**
  2929. * Get SVG path array from array of line segments.
  2930. *
  2931. * @private
  2932. * @function pathFromSegments
  2933. *
  2934. * @param {Array<object>} segments
  2935. * The segments to build the path from.
  2936. *
  2937. * @return {Highcharts.SVGPathArray}
  2938. * SVG path array as accepted by the SVG Renderer.
  2939. */
  2940. function pathFromSegments(segments) {
  2941. var path = [];
  2942. if (segments.length) {
  2943. path.push(['M', segments[0].start.x, segments[0].start.y]);
  2944. for (var i = 0; i < segments.length; ++i) {
  2945. path.push(['L', segments[i].end.x, segments[i].end.y]);
  2946. }
  2947. }
  2948. return path;
  2949. }
  2950. /**
  2951. * Limits obstacle max/mins in all directions to bounds. Modifies input
  2952. * obstacle.
  2953. *
  2954. * @private
  2955. * @function limitObstacleToBounds
  2956. *
  2957. * @param {object} obstacle
  2958. * Obstacle to limit.
  2959. *
  2960. * @param {object} bounds
  2961. * Bounds to use as limit.
  2962. *
  2963. * @return {void}
  2964. */
  2965. function limitObstacleToBounds(obstacle, bounds) {
  2966. obstacle.yMin = max(obstacle.yMin, bounds.yMin);
  2967. obstacle.yMax = min(obstacle.yMax, bounds.yMax);
  2968. obstacle.xMin = max(obstacle.xMin, bounds.xMin);
  2969. obstacle.xMax = min(obstacle.xMax, bounds.xMax);
  2970. }
  2971. // Define the available pathfinding algorithms.
  2972. // Algorithms take up to 3 arguments: starting point, ending point, and an
  2973. // options object.
  2974. var algorithms = {
  2975. /**
  2976. * Get an SVG path from a starting coordinate to an ending coordinate.
  2977. * Draws a straight line.
  2978. *
  2979. * @function Highcharts.Pathfinder.algorithms.straight
  2980. *
  2981. * @param {Highcharts.PositionObject} start
  2982. * Starting coordinate, object with x/y props.
  2983. *
  2984. * @param {Highcharts.PositionObject} end
  2985. * Ending coordinate, object with x/y props.
  2986. *
  2987. * @return {object}
  2988. * An object with the SVG path in Array form as accepted by the SVG
  2989. * renderer, as well as an array of new obstacles making up this
  2990. * path.
  2991. */
  2992. straight: function (start, end) {
  2993. return {
  2994. path: [
  2995. ['M', start.x, start.y],
  2996. ['L', end.x, end.y]
  2997. ],
  2998. obstacles: [{ start: start, end: end }]
  2999. };
  3000. },
  3001. /**
  3002. * Find a path from a starting coordinate to an ending coordinate, using
  3003. * right angles only, and taking only starting/ending obstacle into
  3004. * consideration.
  3005. *
  3006. * @function Highcharts.Pathfinder.algorithms.simpleConnect
  3007. *
  3008. * @param {Highcharts.PositionObject} start
  3009. * Starting coordinate, object with x/y props.
  3010. *
  3011. * @param {Highcharts.PositionObject} end
  3012. * Ending coordinate, object with x/y props.
  3013. *
  3014. * @param {object} options
  3015. * Options for the algorithm:
  3016. * - chartObstacles: Array of chart obstacles to avoid
  3017. * - startDirectionX: Optional. True if starting in the X direction.
  3018. * If not provided, the algorithm starts in the direction that is
  3019. * the furthest between start/end.
  3020. *
  3021. * @return {object}
  3022. * An object with the SVG path in Array form as accepted by the SVG
  3023. * renderer, as well as an array of new obstacles making up this
  3024. * path.
  3025. */
  3026. simpleConnect: extend(function (start, end, options) {
  3027. var segments = [], endSegment, dir = pick(options.startDirectionX, abs(end.x - start.x) > abs(end.y - start.y)) ? 'x' : 'y', chartObstacles = options.chartObstacles, startObstacleIx = findObstacleFromPoint(chartObstacles, start), endObstacleIx = findObstacleFromPoint(chartObstacles, end), startObstacle, endObstacle, prevWaypoint, waypoint, waypoint2, useMax, endPoint;
  3028. // eslint-disable-next-line valid-jsdoc
  3029. /**
  3030. * Return a clone of a point with a property set from a target object,
  3031. * optionally with an offset
  3032. * @private
  3033. */
  3034. function copyFromPoint(from, fromKey, to, toKey, offset) {
  3035. var point = {
  3036. x: from.x,
  3037. y: from.y
  3038. };
  3039. point[fromKey] = to[toKey || fromKey] + (offset || 0);
  3040. return point;
  3041. }
  3042. // eslint-disable-next-line valid-jsdoc
  3043. /**
  3044. * Return waypoint outside obstacle.
  3045. * @private
  3046. */
  3047. function getMeOut(obstacle, point, direction) {
  3048. var useMax = abs(point[direction] - obstacle[direction + 'Min']) >
  3049. abs(point[direction] - obstacle[direction + 'Max']);
  3050. return copyFromPoint(point, direction, obstacle, direction + (useMax ? 'Max' : 'Min'), useMax ? 1 : -1);
  3051. }
  3052. // Pull out end point
  3053. if (endObstacleIx > -1) {
  3054. endObstacle = chartObstacles[endObstacleIx];
  3055. waypoint = getMeOut(endObstacle, end, dir);
  3056. endSegment = {
  3057. start: waypoint,
  3058. end: end
  3059. };
  3060. endPoint = waypoint;
  3061. }
  3062. else {
  3063. endPoint = end;
  3064. }
  3065. // If an obstacle envelops the start point, add a segment to get out,
  3066. // and around it.
  3067. if (startObstacleIx > -1) {
  3068. startObstacle = chartObstacles[startObstacleIx];
  3069. waypoint = getMeOut(startObstacle, start, dir);
  3070. segments.push({
  3071. start: start,
  3072. end: waypoint
  3073. });
  3074. // If we are going back again, switch direction to get around start
  3075. // obstacle.
  3076. if (
  3077. // Going towards max from start:
  3078. waypoint[dir] >= start[dir] ===
  3079. // Going towards min to end:
  3080. waypoint[dir] >= endPoint[dir]) {
  3081. dir = dir === 'y' ? 'x' : 'y';
  3082. useMax = start[dir] < end[dir];
  3083. segments.push({
  3084. start: waypoint,
  3085. end: copyFromPoint(waypoint, dir, startObstacle, dir + (useMax ? 'Max' : 'Min'), useMax ? 1 : -1)
  3086. });
  3087. // Switch direction again
  3088. dir = dir === 'y' ? 'x' : 'y';
  3089. }
  3090. }
  3091. // We are around the start obstacle. Go towards the end in one
  3092. // direction.
  3093. prevWaypoint = segments.length ?
  3094. segments[segments.length - 1].end :
  3095. start;
  3096. waypoint = copyFromPoint(prevWaypoint, dir, endPoint);
  3097. segments.push({
  3098. start: prevWaypoint,
  3099. end: waypoint
  3100. });
  3101. // Final run to end point in the other direction
  3102. dir = dir === 'y' ? 'x' : 'y';
  3103. waypoint2 = copyFromPoint(waypoint, dir, endPoint);
  3104. segments.push({
  3105. start: waypoint,
  3106. end: waypoint2
  3107. });
  3108. // Finally add the endSegment
  3109. segments.push(endSegment);
  3110. return {
  3111. path: pathFromSegments(segments),
  3112. obstacles: segments
  3113. };
  3114. }, {
  3115. requiresObstacles: true
  3116. }),
  3117. /**
  3118. * Find a path from a starting coordinate to an ending coordinate, taking
  3119. * obstacles into consideration. Might not always find the optimal path,
  3120. * but is fast, and usually good enough.
  3121. *
  3122. * @function Highcharts.Pathfinder.algorithms.fastAvoid
  3123. *
  3124. * @param {Highcharts.PositionObject} start
  3125. * Starting coordinate, object with x/y props.
  3126. *
  3127. * @param {Highcharts.PositionObject} end
  3128. * Ending coordinate, object with x/y props.
  3129. *
  3130. * @param {object} options
  3131. * Options for the algorithm.
  3132. * - chartObstacles: Array of chart obstacles to avoid
  3133. * - lineObstacles: Array of line obstacles to jump over
  3134. * - obstacleMetrics: Object with metrics of chartObstacles cached
  3135. * - hardBounds: Hard boundaries to not cross
  3136. * - obstacleOptions: Options for the obstacles, including margin
  3137. * - startDirectionX: Optional. True if starting in the X direction.
  3138. * If not provided, the algorithm starts in the
  3139. * direction that is the furthest between
  3140. * start/end.
  3141. *
  3142. * @return {object}
  3143. * An object with the SVG path in Array form as accepted by the SVG
  3144. * renderer, as well as an array of new obstacles making up this
  3145. * path.
  3146. */
  3147. fastAvoid: extend(function (start, end, options) {
  3148. /*
  3149. Algorithm rules/description
  3150. - Find initial direction
  3151. - Determine soft/hard max for each direction.
  3152. - Move along initial direction until obstacle.
  3153. - Change direction.
  3154. - If hitting obstacle, first try to change length of previous line
  3155. before changing direction again.
  3156. Soft min/max x = start/destination x +/- widest obstacle + margin
  3157. Soft min/max y = start/destination y +/- tallest obstacle + margin
  3158. @todo:
  3159. - Make retrospective, try changing prev segment to reduce
  3160. corners
  3161. - Fix logic for breaking out of end-points - not always picking
  3162. the best direction currently
  3163. - When going around the end obstacle we should not always go the
  3164. shortest route, rather pick the one closer to the end point
  3165. */
  3166. var dirIsX = pick(options.startDirectionX, abs(end.x - start.x) > abs(end.y - start.y)), dir = dirIsX ? 'x' : 'y', segments, useMax, extractedEndPoint, endSegments = [], forceObstacleBreak = false, // Used in clearPathTo to keep track of
  3167. // when to force break through an obstacle.
  3168. // Boundaries to stay within. If beyond soft boundary, prefer to
  3169. // change direction ASAP. If at hard max, always change immediately.
  3170. metrics = options.obstacleMetrics, softMinX = min(start.x, end.x) - metrics.maxWidth - 10, softMaxX = max(start.x, end.x) + metrics.maxWidth + 10, softMinY = min(start.y, end.y) - metrics.maxHeight - 10, softMaxY = max(start.y, end.y) + metrics.maxHeight + 10,
  3171. // Obstacles
  3172. chartObstacles = options.chartObstacles, startObstacleIx = findLastObstacleBefore(chartObstacles, softMinX), endObstacleIx = findLastObstacleBefore(chartObstacles, softMaxX);
  3173. // eslint-disable-next-line valid-jsdoc
  3174. /**
  3175. * How far can you go between two points before hitting an obstacle?
  3176. * Does not work for diagonal lines (because it doesn't have to).
  3177. * @private
  3178. */
  3179. function pivotPoint(fromPoint, toPoint, directionIsX) {
  3180. var firstPoint, lastPoint, highestPoint, lowestPoint, i, searchDirection = fromPoint.x < toPoint.x ? 1 : -1;
  3181. if (fromPoint.x < toPoint.x) {
  3182. firstPoint = fromPoint;
  3183. lastPoint = toPoint;
  3184. }
  3185. else {
  3186. firstPoint = toPoint;
  3187. lastPoint = fromPoint;
  3188. }
  3189. if (fromPoint.y < toPoint.y) {
  3190. lowestPoint = fromPoint;
  3191. highestPoint = toPoint;
  3192. }
  3193. else {
  3194. lowestPoint = toPoint;
  3195. highestPoint = fromPoint;
  3196. }
  3197. // Go through obstacle range in reverse if toPoint is before
  3198. // fromPoint in the X-dimension.
  3199. i = searchDirection < 0 ?
  3200. // Searching backwards, start at last obstacle before last point
  3201. min(findLastObstacleBefore(chartObstacles, lastPoint.x), chartObstacles.length - 1) :
  3202. // Forwards. Since we're not sorted by xMax, we have to look
  3203. // at all obstacles.
  3204. 0;
  3205. // Go through obstacles in this X range
  3206. while (chartObstacles[i] && (searchDirection > 0 && chartObstacles[i].xMin <= lastPoint.x ||
  3207. searchDirection < 0 && chartObstacles[i].xMax >= firstPoint.x)) {
  3208. // If this obstacle is between from and to points in a straight
  3209. // line, pivot at the intersection.
  3210. if (chartObstacles[i].xMin <= lastPoint.x &&
  3211. chartObstacles[i].xMax >= firstPoint.x &&
  3212. chartObstacles[i].yMin <= highestPoint.y &&
  3213. chartObstacles[i].yMax >= lowestPoint.y) {
  3214. if (directionIsX) {
  3215. return {
  3216. y: fromPoint.y,
  3217. x: fromPoint.x < toPoint.x ?
  3218. chartObstacles[i].xMin - 1 :
  3219. chartObstacles[i].xMax + 1,
  3220. obstacle: chartObstacles[i]
  3221. };
  3222. }
  3223. // else ...
  3224. return {
  3225. x: fromPoint.x,
  3226. y: fromPoint.y < toPoint.y ?
  3227. chartObstacles[i].yMin - 1 :
  3228. chartObstacles[i].yMax + 1,
  3229. obstacle: chartObstacles[i]
  3230. };
  3231. }
  3232. i += searchDirection;
  3233. }
  3234. return toPoint;
  3235. }
  3236. /**
  3237. * Decide in which direction to dodge or get out of an obstacle.
  3238. * Considers desired direction, which way is shortest, soft and hard
  3239. * bounds.
  3240. *
  3241. * (? Returns a string, either xMin, xMax, yMin or yMax.)
  3242. *
  3243. * @private
  3244. * @function
  3245. *
  3246. * @param {object} obstacle
  3247. * Obstacle to dodge/escape.
  3248. *
  3249. * @param {object} fromPoint
  3250. * Point with x/y props that's dodging/escaping.
  3251. *
  3252. * @param {object} toPoint
  3253. * Goal point.
  3254. *
  3255. * @param {boolean} dirIsX
  3256. * Dodge in X dimension.
  3257. *
  3258. * @param {object} bounds
  3259. * Hard and soft boundaries.
  3260. *
  3261. * @return {boolean}
  3262. * Use max or not.
  3263. */
  3264. function getDodgeDirection(obstacle, fromPoint, toPoint, dirIsX, bounds) {
  3265. var softBounds = bounds.soft, hardBounds = bounds.hard, dir = dirIsX ? 'x' : 'y', toPointMax = { x: fromPoint.x, y: fromPoint.y }, toPointMin = { x: fromPoint.x, y: fromPoint.y }, minPivot, maxPivot, maxOutOfSoftBounds = obstacle[dir + 'Max'] >=
  3266. softBounds[dir + 'Max'], minOutOfSoftBounds = obstacle[dir + 'Min'] <=
  3267. softBounds[dir + 'Min'], maxOutOfHardBounds = obstacle[dir + 'Max'] >=
  3268. hardBounds[dir + 'Max'], minOutOfHardBounds = obstacle[dir + 'Min'] <=
  3269. hardBounds[dir + 'Min'],
  3270. // Find out if we should prefer one direction over the other if
  3271. // we can choose freely
  3272. minDistance = abs(obstacle[dir + 'Min'] - fromPoint[dir]), maxDistance = abs(obstacle[dir + 'Max'] - fromPoint[dir]),
  3273. // If it's a small difference, pick the one leading towards dest
  3274. // point. Otherwise pick the shortest distance
  3275. useMax = abs(minDistance - maxDistance) < 10 ?
  3276. fromPoint[dir] < toPoint[dir] :
  3277. maxDistance < minDistance;
  3278. // Check if we hit any obstacles trying to go around in either
  3279. // direction.
  3280. toPointMin[dir] = obstacle[dir + 'Min'];
  3281. toPointMax[dir] = obstacle[dir + 'Max'];
  3282. minPivot = pivotPoint(fromPoint, toPointMin, dirIsX)[dir] !==
  3283. toPointMin[dir];
  3284. maxPivot = pivotPoint(fromPoint, toPointMax, dirIsX)[dir] !==
  3285. toPointMax[dir];
  3286. useMax = minPivot ?
  3287. (maxPivot ? useMax : true) :
  3288. (maxPivot ? false : useMax);
  3289. // useMax now contains our preferred choice, bounds not taken into
  3290. // account. If both or neither direction is out of bounds we want to
  3291. // use this.
  3292. // Deal with soft bounds
  3293. useMax = minOutOfSoftBounds ?
  3294. (maxOutOfSoftBounds ? useMax : true) : // Out on min
  3295. (maxOutOfSoftBounds ? false : useMax); // Not out on min
  3296. // Deal with hard bounds
  3297. useMax = minOutOfHardBounds ?
  3298. (maxOutOfHardBounds ? useMax : true) : // Out on min
  3299. (maxOutOfHardBounds ? false : useMax); // Not out on min
  3300. return useMax;
  3301. }
  3302. // eslint-disable-next-line valid-jsdoc
  3303. /**
  3304. * Find a clear path between point.
  3305. * @private
  3306. */
  3307. function clearPathTo(fromPoint, toPoint, dirIsX) {
  3308. // Don't waste time if we've hit goal
  3309. if (fromPoint.x === toPoint.x && fromPoint.y === toPoint.y) {
  3310. return [];
  3311. }
  3312. var dir = dirIsX ? 'x' : 'y', pivot, segments, waypoint, waypointUseMax, envelopingObstacle, secondEnvelopingObstacle, envelopWaypoint, obstacleMargin = options.obstacleOptions.margin, bounds = {
  3313. soft: {
  3314. xMin: softMinX,
  3315. xMax: softMaxX,
  3316. yMin: softMinY,
  3317. yMax: softMaxY
  3318. },
  3319. hard: options.hardBounds
  3320. };
  3321. // If fromPoint is inside an obstacle we have a problem. Break out
  3322. // by just going to the outside of this obstacle. We prefer to go to
  3323. // the nearest edge in the chosen direction.
  3324. envelopingObstacle =
  3325. findObstacleFromPoint(chartObstacles, fromPoint);
  3326. if (envelopingObstacle > -1) {
  3327. envelopingObstacle = chartObstacles[envelopingObstacle];
  3328. waypointUseMax = getDodgeDirection(envelopingObstacle, fromPoint, toPoint, dirIsX, bounds);
  3329. // Cut obstacle to hard bounds to make sure we stay within
  3330. limitObstacleToBounds(envelopingObstacle, options.hardBounds);
  3331. envelopWaypoint = dirIsX ? {
  3332. y: fromPoint.y,
  3333. x: envelopingObstacle[waypointUseMax ? 'xMax' : 'xMin'] +
  3334. (waypointUseMax ? 1 : -1)
  3335. } : {
  3336. x: fromPoint.x,
  3337. y: envelopingObstacle[waypointUseMax ? 'yMax' : 'yMin'] +
  3338. (waypointUseMax ? 1 : -1)
  3339. };
  3340. // If we crashed into another obstacle doing this, we put the
  3341. // waypoint between them instead
  3342. secondEnvelopingObstacle = findObstacleFromPoint(chartObstacles, envelopWaypoint);
  3343. if (secondEnvelopingObstacle > -1) {
  3344. secondEnvelopingObstacle = chartObstacles[secondEnvelopingObstacle];
  3345. // Cut obstacle to hard bounds
  3346. limitObstacleToBounds(secondEnvelopingObstacle, options.hardBounds);
  3347. // Modify waypoint to lay between obstacles
  3348. envelopWaypoint[dir] = waypointUseMax ? max(envelopingObstacle[dir + 'Max'] - obstacleMargin + 1, (secondEnvelopingObstacle[dir + 'Min'] +
  3349. envelopingObstacle[dir + 'Max']) / 2) :
  3350. min((envelopingObstacle[dir + 'Min'] + obstacleMargin - 1), ((secondEnvelopingObstacle[dir + 'Max'] +
  3351. envelopingObstacle[dir + 'Min']) / 2));
  3352. // We are not going anywhere. If this happens for the first
  3353. // time, do nothing. Otherwise, try to go to the extreme of
  3354. // the obstacle pair in the current direction.
  3355. if (fromPoint.x === envelopWaypoint.x &&
  3356. fromPoint.y === envelopWaypoint.y) {
  3357. if (forceObstacleBreak) {
  3358. envelopWaypoint[dir] = waypointUseMax ?
  3359. max(envelopingObstacle[dir + 'Max'], secondEnvelopingObstacle[dir + 'Max']) + 1 :
  3360. min(envelopingObstacle[dir + 'Min'], secondEnvelopingObstacle[dir + 'Min']) - 1;
  3361. }
  3362. // Toggle on if off, and the opposite
  3363. forceObstacleBreak = !forceObstacleBreak;
  3364. }
  3365. else {
  3366. // This point is not identical to previous.
  3367. // Clear break trigger.
  3368. forceObstacleBreak = false;
  3369. }
  3370. }
  3371. segments = [{
  3372. start: fromPoint,
  3373. end: envelopWaypoint
  3374. }];
  3375. }
  3376. else { // If not enveloping, use standard pivot calculation
  3377. pivot = pivotPoint(fromPoint, {
  3378. x: dirIsX ? toPoint.x : fromPoint.x,
  3379. y: dirIsX ? fromPoint.y : toPoint.y
  3380. }, dirIsX);
  3381. segments = [{
  3382. start: fromPoint,
  3383. end: {
  3384. x: pivot.x,
  3385. y: pivot.y
  3386. }
  3387. }];
  3388. // Pivot before goal, use a waypoint to dodge obstacle
  3389. if (pivot[dirIsX ? 'x' : 'y'] !== toPoint[dirIsX ? 'x' : 'y']) {
  3390. // Find direction of waypoint
  3391. waypointUseMax = getDodgeDirection(pivot.obstacle, pivot, toPoint, !dirIsX, bounds);
  3392. // Cut waypoint to hard bounds
  3393. limitObstacleToBounds(pivot.obstacle, options.hardBounds);
  3394. waypoint = {
  3395. x: dirIsX ?
  3396. pivot.x :
  3397. pivot.obstacle[waypointUseMax ? 'xMax' : 'xMin'] +
  3398. (waypointUseMax ? 1 : -1),
  3399. y: dirIsX ?
  3400. pivot.obstacle[waypointUseMax ? 'yMax' : 'yMin'] +
  3401. (waypointUseMax ? 1 : -1) :
  3402. pivot.y
  3403. };
  3404. // We're changing direction here, store that to make sure we
  3405. // also change direction when adding the last segment array
  3406. // after handling waypoint.
  3407. dirIsX = !dirIsX;
  3408. segments = segments.concat(clearPathTo({
  3409. x: pivot.x,
  3410. y: pivot.y
  3411. }, waypoint, dirIsX));
  3412. }
  3413. }
  3414. // Get segments for the other direction too
  3415. // Recursion is our friend
  3416. segments = segments.concat(clearPathTo(segments[segments.length - 1].end, toPoint, !dirIsX));
  3417. return segments;
  3418. }
  3419. // eslint-disable-next-line valid-jsdoc
  3420. /**
  3421. * Extract point to outside of obstacle in whichever direction is
  3422. * closest. Returns new point outside obstacle.
  3423. * @private
  3424. */
  3425. function extractFromObstacle(obstacle, point, goalPoint) {
  3426. var dirIsX = min(obstacle.xMax - point.x, point.x - obstacle.xMin) <
  3427. min(obstacle.yMax - point.y, point.y - obstacle.yMin), bounds = {
  3428. soft: options.hardBounds,
  3429. hard: options.hardBounds
  3430. }, useMax = getDodgeDirection(obstacle, point, goalPoint, dirIsX, bounds);
  3431. return dirIsX ? {
  3432. y: point.y,
  3433. x: obstacle[useMax ? 'xMax' : 'xMin'] + (useMax ? 1 : -1)
  3434. } : {
  3435. x: point.x,
  3436. y: obstacle[useMax ? 'yMax' : 'yMin'] + (useMax ? 1 : -1)
  3437. };
  3438. }
  3439. // Cut the obstacle array to soft bounds for optimization in large
  3440. // datasets.
  3441. chartObstacles =
  3442. chartObstacles.slice(startObstacleIx, endObstacleIx + 1);
  3443. // If an obstacle envelops the end point, move it out of there and add
  3444. // a little segment to where it was.
  3445. if ((endObstacleIx = findObstacleFromPoint(chartObstacles, end)) > -1) {
  3446. extractedEndPoint = extractFromObstacle(chartObstacles[endObstacleIx], end, start);
  3447. endSegments.push({
  3448. end: end,
  3449. start: extractedEndPoint
  3450. });
  3451. end = extractedEndPoint;
  3452. }
  3453. // If it's still inside one or more obstacles, get out of there by
  3454. // force-moving towards the start point.
  3455. while ((endObstacleIx = findObstacleFromPoint(chartObstacles, end)) > -1) {
  3456. useMax = end[dir] - start[dir] < 0;
  3457. extractedEndPoint = {
  3458. x: end.x,
  3459. y: end.y
  3460. };
  3461. extractedEndPoint[dir] = chartObstacles[endObstacleIx][useMax ? dir + 'Max' : dir + 'Min'] + (useMax ? 1 : -1);
  3462. endSegments.push({
  3463. end: end,
  3464. start: extractedEndPoint
  3465. });
  3466. end = extractedEndPoint;
  3467. }
  3468. // Find the path
  3469. segments = clearPathTo(start, end, dirIsX);
  3470. // Add the end-point segments
  3471. segments = segments.concat(endSegments.reverse());
  3472. return {
  3473. path: pathFromSegments(segments),
  3474. obstacles: segments
  3475. };
  3476. }, {
  3477. requiresObstacles: true
  3478. })
  3479. };
  3480. return algorithms;
  3481. });
  3482. _registerModule(_modules, 'parts-gantt/ArrowSymbols.js', [_modules['parts/SVGRenderer.js']], function (SVGRenderer) {
  3483. /* *
  3484. *
  3485. * (c) 2017 Highsoft AS
  3486. * Authors: Lars A. V. Cabrera
  3487. *
  3488. * License: www.highcharts.com/license
  3489. *
  3490. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  3491. *
  3492. * */
  3493. /**
  3494. * Creates an arrow symbol. Like a triangle, except not filled.
  3495. * ```
  3496. * o
  3497. * o
  3498. * o
  3499. * o
  3500. * o
  3501. * o
  3502. * o
  3503. * ```
  3504. *
  3505. * @private
  3506. * @function
  3507. *
  3508. * @param {number} x
  3509. * x position of the arrow
  3510. *
  3511. * @param {number} y
  3512. * y position of the arrow
  3513. *
  3514. * @param {number} w
  3515. * width of the arrow
  3516. *
  3517. * @param {number} h
  3518. * height of the arrow
  3519. *
  3520. * @return {Highcharts.SVGPathArray}
  3521. * Path array
  3522. */
  3523. SVGRenderer.prototype.symbols.arrow = function (x, y, w, h) {
  3524. return [
  3525. ['M', x, y + h / 2],
  3526. ['L', x + w, y],
  3527. ['L', x, y + h / 2],
  3528. ['L', x + w, y + h]
  3529. ];
  3530. };
  3531. /**
  3532. * Creates a half-width arrow symbol. Like a triangle, except not filled.
  3533. * ```
  3534. * o
  3535. * o
  3536. * o
  3537. * o
  3538. * o
  3539. * ```
  3540. *
  3541. * @private
  3542. * @function
  3543. *
  3544. * @param {number} x
  3545. * x position of the arrow
  3546. *
  3547. * @param {number} y
  3548. * y position of the arrow
  3549. *
  3550. * @param {number} w
  3551. * width of the arrow
  3552. *
  3553. * @param {number} h
  3554. * height of the arrow
  3555. *
  3556. * @return {Highcharts.SVGPathArray}
  3557. * Path array
  3558. */
  3559. SVGRenderer.prototype.symbols['arrow-half'] = function (x, y, w, h) {
  3560. return SVGRenderer.prototype.symbols.arrow(x, y, w / 2, h);
  3561. };
  3562. /**
  3563. * Creates a left-oriented triangle.
  3564. * ```
  3565. * o
  3566. * ooooooo
  3567. * ooooooooooooo
  3568. * ooooooo
  3569. * o
  3570. * ```
  3571. *
  3572. * @private
  3573. * @function
  3574. *
  3575. * @param {number} x
  3576. * x position of the triangle
  3577. *
  3578. * @param {number} y
  3579. * y position of the triangle
  3580. *
  3581. * @param {number} w
  3582. * width of the triangle
  3583. *
  3584. * @param {number} h
  3585. * height of the triangle
  3586. *
  3587. * @return {Highcharts.SVGPathArray}
  3588. * Path array
  3589. */
  3590. SVGRenderer.prototype.symbols['triangle-left'] = function (x, y, w, h) {
  3591. return [
  3592. ['M', x + w, y],
  3593. ['L', x, y + h / 2],
  3594. ['L', x + w, y + h],
  3595. ['Z']
  3596. ];
  3597. };
  3598. /**
  3599. * Alias function for triangle-left.
  3600. *
  3601. * @private
  3602. * @function
  3603. *
  3604. * @param {number} x
  3605. * x position of the arrow
  3606. *
  3607. * @param {number} y
  3608. * y position of the arrow
  3609. *
  3610. * @param {number} w
  3611. * width of the arrow
  3612. *
  3613. * @param {number} h
  3614. * height of the arrow
  3615. *
  3616. * @return {Highcharts.SVGPathArray}
  3617. * Path array
  3618. */
  3619. SVGRenderer.prototype.symbols['arrow-filled'] = SVGRenderer.prototype.symbols['triangle-left'];
  3620. /**
  3621. * Creates a half-width, left-oriented triangle.
  3622. * ```
  3623. * o
  3624. * oooo
  3625. * ooooooo
  3626. * oooo
  3627. * o
  3628. * ```
  3629. *
  3630. * @private
  3631. * @function
  3632. *
  3633. * @param {number} x
  3634. * x position of the triangle
  3635. *
  3636. * @param {number} y
  3637. * y position of the triangle
  3638. *
  3639. * @param {number} w
  3640. * width of the triangle
  3641. *
  3642. * @param {number} h
  3643. * height of the triangle
  3644. *
  3645. * @return {Highcharts.SVGPathArray}
  3646. * Path array
  3647. */
  3648. SVGRenderer.prototype.symbols['triangle-left-half'] = function (x, y, w, h) {
  3649. return SVGRenderer.prototype.symbols['triangle-left'](x, y, w / 2, h);
  3650. };
  3651. /**
  3652. * Alias function for triangle-left-half.
  3653. *
  3654. * @private
  3655. * @function
  3656. *
  3657. * @param {number} x
  3658. * x position of the arrow
  3659. *
  3660. * @param {number} y
  3661. * y position of the arrow
  3662. *
  3663. * @param {number} w
  3664. * width of the arrow
  3665. *
  3666. * @param {number} h
  3667. * height of the arrow
  3668. *
  3669. * @return {Highcharts.SVGPathArray}
  3670. * Path array
  3671. */
  3672. SVGRenderer.prototype.symbols['arrow-filled-half'] = SVGRenderer.prototype.symbols['triangle-left-half'];
  3673. });
  3674. _registerModule(_modules, 'parts-gantt/Pathfinder.js', [_modules['parts/Chart.js'], _modules['parts/Globals.js'], _modules['parts/Options.js'], _modules['parts/Point.js'], _modules['parts/Utilities.js'], _modules['parts-gantt/PathfinderAlgorithms.js']], function (Chart, H, O, Point, U, pathfinderAlgorithms) {
  3675. /* *
  3676. *
  3677. * (c) 2016 Highsoft AS
  3678. * Authors: Øystein Moseng, Lars A. V. Cabrera
  3679. *
  3680. * License: www.highcharts.com/license
  3681. *
  3682. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  3683. *
  3684. * */
  3685. /**
  3686. * The default pathfinder algorithm to use for a chart. It is possible to define
  3687. * your own algorithms by adding them to the
  3688. * `Highcharts.Pathfinder.prototype.algorithms`
  3689. * object before the chart has been created.
  3690. *
  3691. * The default algorithms are as follows:
  3692. *
  3693. * `straight`: Draws a straight line between the connecting
  3694. * points. Does not avoid other points when drawing.
  3695. *
  3696. * `simpleConnect`: Finds a path between the points using right angles
  3697. * only. Takes only starting/ending points into
  3698. * account, and will not avoid other points.
  3699. *
  3700. * `fastAvoid`: Finds a path between the points using right angles
  3701. * only. Will attempt to avoid other points, but its
  3702. * focus is performance over accuracy. Works well with
  3703. * less dense datasets.
  3704. *
  3705. * @typedef {"fastAvoid"|"simpleConnect"|"straight"|string} Highcharts.PathfinderTypeValue
  3706. */
  3707. ''; // detach doclets above
  3708. var defaultOptions = O.defaultOptions;
  3709. var addEvent = U.addEvent, defined = U.defined, error = U.error, extend = U.extend, merge = U.merge, objectEach = U.objectEach, pick = U.pick, splat = U.splat;
  3710. var deg2rad = H.deg2rad, max = Math.max, min = Math.min;
  3711. /*
  3712. @todo:
  3713. - Document how to write your own algorithms
  3714. - Consider adding a Point.pathTo method that wraps creating a connection
  3715. and rendering it
  3716. */
  3717. // Set default Pathfinder options
  3718. extend(defaultOptions, {
  3719. /**
  3720. * The Pathfinder module allows you to define connections between any two
  3721. * points, represented as lines - optionally with markers for the start
  3722. * and/or end points. Multiple algorithms are available for calculating how
  3723. * the connecting lines are drawn.
  3724. *
  3725. * Connector functionality requires Highcharts Gantt to be loaded. In Gantt
  3726. * charts, the connectors are used to draw dependencies between tasks.
  3727. *
  3728. * @see [dependency](series.gantt.data.dependency)
  3729. *
  3730. * @sample gantt/pathfinder/demo
  3731. * Pathfinder connections
  3732. *
  3733. * @declare Highcharts.ConnectorsOptions
  3734. * @product gantt
  3735. * @optionparent connectors
  3736. */
  3737. connectors: {
  3738. /**
  3739. * Enable connectors for this chart. Requires Highcharts Gantt.
  3740. *
  3741. * @type {boolean}
  3742. * @default true
  3743. * @since 6.2.0
  3744. * @apioption connectors.enabled
  3745. */
  3746. /**
  3747. * Set the default dash style for this chart's connecting lines.
  3748. *
  3749. * @type {string}
  3750. * @default solid
  3751. * @since 6.2.0
  3752. * @apioption connectors.dashStyle
  3753. */
  3754. /**
  3755. * Set the default color for this chart's Pathfinder connecting lines.
  3756. * Defaults to the color of the point being connected.
  3757. *
  3758. * @type {Highcharts.ColorString}
  3759. * @since 6.2.0
  3760. * @apioption connectors.lineColor
  3761. */
  3762. /**
  3763. * Set the default pathfinder margin to use, in pixels. Some Pathfinder
  3764. * algorithms attempt to avoid obstacles, such as other points in the
  3765. * chart. These algorithms use this margin to determine how close lines
  3766. * can be to an obstacle. The default is to compute this automatically
  3767. * from the size of the obstacles in the chart.
  3768. *
  3769. * To draw connecting lines close to existing points, set this to a low
  3770. * number. For more space around existing points, set this number
  3771. * higher.
  3772. *
  3773. * @sample gantt/pathfinder/algorithm-margin
  3774. * Small algorithmMargin
  3775. *
  3776. * @type {number}
  3777. * @since 6.2.0
  3778. * @apioption connectors.algorithmMargin
  3779. */
  3780. /**
  3781. * Set the default pathfinder algorithm to use for this chart. It is
  3782. * possible to define your own algorithms by adding them to the
  3783. * Highcharts.Pathfinder.prototype.algorithms object before the chart
  3784. * has been created.
  3785. *
  3786. * The default algorithms are as follows:
  3787. *
  3788. * `straight`: Draws a straight line between the connecting
  3789. * points. Does not avoid other points when drawing.
  3790. *
  3791. * `simpleConnect`: Finds a path between the points using right angles
  3792. * only. Takes only starting/ending points into
  3793. * account, and will not avoid other points.
  3794. *
  3795. * `fastAvoid`: Finds a path between the points using right angles
  3796. * only. Will attempt to avoid other points, but its
  3797. * focus is performance over accuracy. Works well with
  3798. * less dense datasets.
  3799. *
  3800. * Default value: `straight` is used as default for most series types,
  3801. * while `simpleConnect` is used as default for Gantt series, to show
  3802. * dependencies between points.
  3803. *
  3804. * @sample gantt/pathfinder/demo
  3805. * Different types used
  3806. *
  3807. * @type {Highcharts.PathfinderTypeValue}
  3808. * @default undefined
  3809. * @since 6.2.0
  3810. */
  3811. type: 'straight',
  3812. /**
  3813. * Set the default pixel width for this chart's Pathfinder connecting
  3814. * lines.
  3815. *
  3816. * @since 6.2.0
  3817. */
  3818. lineWidth: 1,
  3819. /**
  3820. * Marker options for this chart's Pathfinder connectors. Note that
  3821. * this option is overridden by the `startMarker` and `endMarker`
  3822. * options.
  3823. *
  3824. * @declare Highcharts.ConnectorsMarkerOptions
  3825. * @since 6.2.0
  3826. */
  3827. marker: {
  3828. /**
  3829. * Set the radius of the connector markers. The default is
  3830. * automatically computed based on the algorithmMargin setting.
  3831. *
  3832. * Setting marker.width and marker.height will override this
  3833. * setting.
  3834. *
  3835. * @type {number}
  3836. * @since 6.2.0
  3837. * @apioption connectors.marker.radius
  3838. */
  3839. /**
  3840. * Set the width of the connector markers. If not supplied, this
  3841. * is inferred from the marker radius.
  3842. *
  3843. * @type {number}
  3844. * @since 6.2.0
  3845. * @apioption connectors.marker.width
  3846. */
  3847. /**
  3848. * Set the height of the connector markers. If not supplied, this
  3849. * is inferred from the marker radius.
  3850. *
  3851. * @type {number}
  3852. * @since 6.2.0
  3853. * @apioption connectors.marker.height
  3854. */
  3855. /**
  3856. * Set the color of the connector markers. By default this is the
  3857. * same as the connector color.
  3858. *
  3859. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  3860. * @since 6.2.0
  3861. * @apioption connectors.marker.color
  3862. */
  3863. /**
  3864. * Set the line/border color of the connector markers. By default
  3865. * this is the same as the marker color.
  3866. *
  3867. * @type {Highcharts.ColorString}
  3868. * @since 6.2.0
  3869. * @apioption connectors.marker.lineColor
  3870. */
  3871. /**
  3872. * Enable markers for the connectors.
  3873. */
  3874. enabled: false,
  3875. /**
  3876. * Horizontal alignment of the markers relative to the points.
  3877. *
  3878. * @type {Highcharts.AlignValue}
  3879. */
  3880. align: 'center',
  3881. /**
  3882. * Vertical alignment of the markers relative to the points.
  3883. *
  3884. * @type {Highcharts.VerticalAlignValue}
  3885. */
  3886. verticalAlign: 'middle',
  3887. /**
  3888. * Whether or not to draw the markers inside the points.
  3889. */
  3890. inside: false,
  3891. /**
  3892. * Set the line/border width of the pathfinder markers.
  3893. */
  3894. lineWidth: 1
  3895. },
  3896. /**
  3897. * Marker options specific to the start markers for this chart's
  3898. * Pathfinder connectors. Overrides the generic marker options.
  3899. *
  3900. * @declare Highcharts.ConnectorsStartMarkerOptions
  3901. * @extends connectors.marker
  3902. * @since 6.2.0
  3903. */
  3904. startMarker: {
  3905. /**
  3906. * Set the symbol of the connector start markers.
  3907. */
  3908. symbol: 'diamond'
  3909. },
  3910. /**
  3911. * Marker options specific to the end markers for this chart's
  3912. * Pathfinder connectors. Overrides the generic marker options.
  3913. *
  3914. * @declare Highcharts.ConnectorsEndMarkerOptions
  3915. * @extends connectors.marker
  3916. * @since 6.2.0
  3917. */
  3918. endMarker: {
  3919. /**
  3920. * Set the symbol of the connector end markers.
  3921. */
  3922. symbol: 'arrow-filled'
  3923. }
  3924. }
  3925. });
  3926. /**
  3927. * Override Pathfinder connector options for a series. Requires Highcharts Gantt
  3928. * to be loaded.
  3929. *
  3930. * @declare Highcharts.SeriesConnectorsOptionsObject
  3931. * @extends connectors
  3932. * @since 6.2.0
  3933. * @excluding enabled, algorithmMargin
  3934. * @product gantt
  3935. * @apioption plotOptions.series.connectors
  3936. */
  3937. /**
  3938. * Connect to a point. This option can be either a string, referring to the ID
  3939. * of another point, or an object, or an array of either. If the option is an
  3940. * array, each element defines a connection.
  3941. *
  3942. * @sample gantt/pathfinder/demo
  3943. * Different connection types
  3944. *
  3945. * @declare Highcharts.XrangePointConnectorsOptionsObject
  3946. * @type {string|Array<string|*>|*}
  3947. * @extends plotOptions.series.connectors
  3948. * @since 6.2.0
  3949. * @excluding enabled
  3950. * @product gantt
  3951. * @requires highcharts-gantt
  3952. * @apioption series.xrange.data.connect
  3953. */
  3954. /**
  3955. * The ID of the point to connect to.
  3956. *
  3957. * @type {string}
  3958. * @since 6.2.0
  3959. * @product gantt
  3960. * @apioption series.xrange.data.connect.to
  3961. */
  3962. /**
  3963. * Get point bounding box using plotX/plotY and shapeArgs. If using
  3964. * graphic.getBBox() directly, the bbox will be affected by animation.
  3965. *
  3966. * @private
  3967. * @function
  3968. *
  3969. * @param {Highcharts.Point} point
  3970. * The point to get BB of.
  3971. *
  3972. * @return {Highcharts.Dictionary<number>|null}
  3973. * Result xMax, xMin, yMax, yMin.
  3974. */
  3975. function getPointBB(point) {
  3976. var shapeArgs = point.shapeArgs, bb;
  3977. // Prefer using shapeArgs (columns)
  3978. if (shapeArgs) {
  3979. return {
  3980. xMin: shapeArgs.x,
  3981. xMax: shapeArgs.x + shapeArgs.width,
  3982. yMin: shapeArgs.y,
  3983. yMax: shapeArgs.y + shapeArgs.height
  3984. };
  3985. }
  3986. // Otherwise use plotX/plotY and bb
  3987. bb = point.graphic && point.graphic.getBBox();
  3988. return bb ? {
  3989. xMin: point.plotX - bb.width / 2,
  3990. xMax: point.plotX + bb.width / 2,
  3991. yMin: point.plotY - bb.height / 2,
  3992. yMax: point.plotY + bb.height / 2
  3993. } : null;
  3994. }
  3995. /**
  3996. * Calculate margin to place around obstacles for the pathfinder in pixels.
  3997. * Returns a minimum of 1 pixel margin.
  3998. *
  3999. * @private
  4000. * @function
  4001. *
  4002. * @param {Array<object>} obstacles
  4003. * Obstacles to calculate margin from.
  4004. *
  4005. * @return {number}
  4006. * The calculated margin in pixels. At least 1.
  4007. */
  4008. function calculateObstacleMargin(obstacles) {
  4009. var len = obstacles.length, i = 0, j, obstacleDistance, distances = [],
  4010. // Compute smallest distance between two rectangles
  4011. distance = function (a, b, bbMargin) {
  4012. // Count the distance even if we are slightly off
  4013. var margin = pick(bbMargin, 10), yOverlap = a.yMax + margin > b.yMin - margin &&
  4014. a.yMin - margin < b.yMax + margin, xOverlap = a.xMax + margin > b.xMin - margin &&
  4015. a.xMin - margin < b.xMax + margin, xDistance = yOverlap ? (a.xMin > b.xMax ? a.xMin - b.xMax : b.xMin - a.xMax) : Infinity, yDistance = xOverlap ? (a.yMin > b.yMax ? a.yMin - b.yMax : b.yMin - a.yMax) : Infinity;
  4016. // If the rectangles collide, try recomputing with smaller margin.
  4017. // If they collide anyway, discard the obstacle.
  4018. if (xOverlap && yOverlap) {
  4019. return (margin ?
  4020. distance(a, b, Math.floor(margin / 2)) :
  4021. Infinity);
  4022. }
  4023. return min(xDistance, yDistance);
  4024. };
  4025. // Go over all obstacles and compare them to the others.
  4026. for (; i < len; ++i) {
  4027. // Compare to all obstacles ahead. We will already have compared this
  4028. // obstacle to the ones before.
  4029. for (j = i + 1; j < len; ++j) {
  4030. obstacleDistance = distance(obstacles[i], obstacles[j]);
  4031. // TODO: Magic number 80
  4032. if (obstacleDistance < 80) { // Ignore large distances
  4033. distances.push(obstacleDistance);
  4034. }
  4035. }
  4036. }
  4037. // Ensure we always have at least one value, even in very spaceous charts
  4038. distances.push(80);
  4039. return max(Math.floor(distances.sort(function (a, b) {
  4040. return (a - b);
  4041. })[
  4042. // Discard first 10% of the relevant distances, and then grab
  4043. // the smallest one.
  4044. Math.floor(distances.length / 10)] / 2 - 1 // Divide the distance by 2 and subtract 1.
  4045. ), 1 // 1 is the minimum margin
  4046. );
  4047. }
  4048. /* eslint-disable no-invalid-this, valid-jsdoc */
  4049. /**
  4050. * The Connection class. Used internally to represent a connection between two
  4051. * points.
  4052. *
  4053. * @private
  4054. * @class
  4055. * @name Highcharts.Connection
  4056. *
  4057. * @param {Highcharts.Point} from
  4058. * Connection runs from this Point.
  4059. *
  4060. * @param {Highcharts.Point} to
  4061. * Connection runs to this Point.
  4062. *
  4063. * @param {Highcharts.ConnectorsOptions} [options]
  4064. * Connection options.
  4065. */
  4066. function Connection(from, to, options) {
  4067. this.init(from, to, options);
  4068. }
  4069. Connection.prototype = {
  4070. /**
  4071. * Initialize the Connection object. Used as constructor only.
  4072. *
  4073. * @function Highcharts.Connection#init
  4074. *
  4075. * @param {Highcharts.Point} from
  4076. * Connection runs from this Point.
  4077. *
  4078. * @param {Highcharts.Point} to
  4079. * Connection runs to this Point.
  4080. *
  4081. * @param {Highcharts.ConnectorsOptions} [options]
  4082. * Connection options.
  4083. */
  4084. init: function (from, to, options) {
  4085. this.fromPoint = from;
  4086. this.toPoint = to;
  4087. this.options = options;
  4088. this.chart = from.series.chart;
  4089. this.pathfinder = this.chart.pathfinder;
  4090. },
  4091. /**
  4092. * Add (or update) this connection's path on chart. Stores reference to the
  4093. * created element on this.graphics.path.
  4094. *
  4095. * @function Highcharts.Connection#renderPath
  4096. *
  4097. * @param {Highcharts.SVGPathArray} path
  4098. * Path to render, in array format. E.g. ['M', 0, 0, 'L', 10, 10]
  4099. *
  4100. * @param {Highcharts.SVGAttributes} [attribs]
  4101. * SVG attributes for the path.
  4102. *
  4103. * @param {Highcharts.AnimationOptionsObject} [animation]
  4104. * Animation options for the rendering.
  4105. */
  4106. renderPath: function (path, attribs, animation) {
  4107. var connection = this, chart = this.chart, styledMode = chart.styledMode, pathfinder = chart.pathfinder, animate = !chart.options.chart.forExport && animation !== false, pathGraphic = connection.graphics && connection.graphics.path, anim;
  4108. // Add the SVG element of the pathfinder group if it doesn't exist
  4109. if (!pathfinder.group) {
  4110. pathfinder.group = chart.renderer.g()
  4111. .addClass('highcharts-pathfinder-group')
  4112. .attr({ zIndex: -1 })
  4113. .add(chart.seriesGroup);
  4114. }
  4115. // Shift the group to compensate for plot area.
  4116. // Note: Do this always (even when redrawing a path) to avoid issues
  4117. // when updating chart in a way that changes plot metrics.
  4118. pathfinder.group.translate(chart.plotLeft, chart.plotTop);
  4119. // Create path if does not exist
  4120. if (!(pathGraphic && pathGraphic.renderer)) {
  4121. pathGraphic = chart.renderer.path()
  4122. .add(pathfinder.group);
  4123. if (!styledMode) {
  4124. pathGraphic.attr({
  4125. opacity: 0
  4126. });
  4127. }
  4128. }
  4129. // Set path attribs and animate to the new path
  4130. pathGraphic.attr(attribs);
  4131. anim = { d: path };
  4132. if (!styledMode) {
  4133. anim.opacity = 1;
  4134. }
  4135. pathGraphic[animate ? 'animate' : 'attr'](anim, animation);
  4136. // Store reference on connection
  4137. this.graphics = this.graphics || {};
  4138. this.graphics.path = pathGraphic;
  4139. },
  4140. /**
  4141. * Calculate and add marker graphics for connection to the chart. The
  4142. * created/updated elements are stored on this.graphics.start and
  4143. * this.graphics.end.
  4144. *
  4145. * @function Highcharts.Connection#addMarker
  4146. *
  4147. * @param {string} type
  4148. * Marker type, either 'start' or 'end'.
  4149. *
  4150. * @param {Highcharts.ConnectorsMarkerOptions} options
  4151. * All options for this marker. Not calculated or merged with other
  4152. * options.
  4153. *
  4154. * @param {Highcharts.SVGPathArray} path
  4155. * Connection path in array format. This is used to calculate the
  4156. * rotation angle of the markers.
  4157. */
  4158. addMarker: function (type, options, path) {
  4159. var connection = this, chart = connection.fromPoint.series.chart, pathfinder = chart.pathfinder, renderer = chart.renderer, point = (type === 'start' ?
  4160. connection.fromPoint :
  4161. connection.toPoint), anchor = point.getPathfinderAnchorPoint(options), markerVector, radians, rotation, box, width, height, pathVector, segment;
  4162. if (!options.enabled) {
  4163. return;
  4164. }
  4165. // Last vector before start/end of path, used to get angle
  4166. if (type === 'start') {
  4167. segment = path[1];
  4168. }
  4169. else { // 'end'
  4170. segment = path[path.length - 2];
  4171. }
  4172. if (segment && segment[0] === 'M' || segment[0] === 'L') {
  4173. pathVector = {
  4174. x: segment[1],
  4175. y: segment[2]
  4176. };
  4177. // Get angle between pathVector and anchor point and use it to
  4178. // create marker position.
  4179. radians = point.getRadiansToVector(pathVector, anchor);
  4180. markerVector = point.getMarkerVector(radians, options.radius, anchor);
  4181. // Rotation of marker is calculated from angle between pathVector
  4182. // and markerVector.
  4183. // (Note:
  4184. // Used to recalculate radians between markerVector and pathVector,
  4185. // but this should be the same as between pathVector and anchor.)
  4186. rotation = -radians / deg2rad;
  4187. if (options.width && options.height) {
  4188. width = options.width;
  4189. height = options.height;
  4190. }
  4191. else {
  4192. width = height = options.radius * 2;
  4193. }
  4194. // Add graphics object if it does not exist
  4195. connection.graphics = connection.graphics || {};
  4196. box = {
  4197. x: markerVector.x - (width / 2),
  4198. y: markerVector.y - (height / 2),
  4199. width: width,
  4200. height: height,
  4201. rotation: rotation,
  4202. rotationOriginX: markerVector.x,
  4203. rotationOriginY: markerVector.y
  4204. };
  4205. if (!connection.graphics[type]) {
  4206. // Create new marker element
  4207. connection.graphics[type] = renderer
  4208. .symbol(options.symbol)
  4209. .addClass('highcharts-point-connecting-path-' + type + '-marker')
  4210. .attr(box)
  4211. .add(pathfinder.group);
  4212. if (!renderer.styledMode) {
  4213. connection.graphics[type].attr({
  4214. fill: options.color || connection.fromPoint.color,
  4215. stroke: options.lineColor,
  4216. 'stroke-width': options.lineWidth,
  4217. opacity: 0
  4218. })
  4219. .animate({
  4220. opacity: 1
  4221. }, point.series.options.animation);
  4222. }
  4223. }
  4224. else {
  4225. connection.graphics[type].animate(box);
  4226. }
  4227. }
  4228. },
  4229. /**
  4230. * Calculate and return connection path.
  4231. * Note: Recalculates chart obstacles on demand if they aren't calculated.
  4232. *
  4233. * @function Highcharts.Connection#getPath
  4234. *
  4235. * @param {Highcharts.ConnectorsOptions} options
  4236. * Connector options. Not calculated or merged with other options.
  4237. *
  4238. * @return {object|undefined}
  4239. * Calculated SVG path data in array format.
  4240. */
  4241. getPath: function (options) {
  4242. var pathfinder = this.pathfinder, chart = this.chart, algorithm = pathfinder.algorithms[options.type], chartObstacles = pathfinder.chartObstacles;
  4243. if (typeof algorithm !== 'function') {
  4244. error('"' + options.type + '" is not a Pathfinder algorithm.');
  4245. return;
  4246. }
  4247. // This function calculates obstacles on demand if they don't exist
  4248. if (algorithm.requiresObstacles && !chartObstacles) {
  4249. chartObstacles =
  4250. pathfinder.chartObstacles =
  4251. pathfinder.getChartObstacles(options);
  4252. // If the algorithmMargin was computed, store the result in default
  4253. // options.
  4254. chart.options.connectors.algorithmMargin =
  4255. options.algorithmMargin;
  4256. // Cache some metrics too
  4257. pathfinder.chartObstacleMetrics =
  4258. pathfinder.getObstacleMetrics(chartObstacles);
  4259. }
  4260. // Get the SVG path
  4261. return algorithm(
  4262. // From
  4263. this.fromPoint.getPathfinderAnchorPoint(options.startMarker),
  4264. // To
  4265. this.toPoint.getPathfinderAnchorPoint(options.endMarker), merge({
  4266. chartObstacles: chartObstacles,
  4267. lineObstacles: pathfinder.lineObstacles || [],
  4268. obstacleMetrics: pathfinder.chartObstacleMetrics,
  4269. hardBounds: {
  4270. xMin: 0,
  4271. xMax: chart.plotWidth,
  4272. yMin: 0,
  4273. yMax: chart.plotHeight
  4274. },
  4275. obstacleOptions: {
  4276. margin: options.algorithmMargin
  4277. },
  4278. startDirectionX: pathfinder.getAlgorithmStartDirection(options.startMarker)
  4279. }, options));
  4280. },
  4281. /**
  4282. * (re)Calculate and (re)draw the connection.
  4283. *
  4284. * @function Highcharts.Connection#render
  4285. */
  4286. render: function () {
  4287. var connection = this, fromPoint = connection.fromPoint, series = fromPoint.series, chart = series.chart, pathfinder = chart.pathfinder, pathResult, path, options = merge(chart.options.connectors, series.options.connectors, fromPoint.options.connectors, connection.options), attribs = {};
  4288. // Set path attribs
  4289. if (!chart.styledMode) {
  4290. attribs.stroke = options.lineColor || fromPoint.color;
  4291. attribs['stroke-width'] = options.lineWidth;
  4292. if (options.dashStyle) {
  4293. attribs.dashstyle = options.dashStyle;
  4294. }
  4295. }
  4296. attribs['class'] = // eslint-disable-line dot-notation
  4297. 'highcharts-point-connecting-path ' +
  4298. 'highcharts-color-' + fromPoint.colorIndex;
  4299. options = merge(attribs, options);
  4300. // Set common marker options
  4301. if (!defined(options.marker.radius)) {
  4302. options.marker.radius = min(max(Math.ceil((options.algorithmMargin || 8) / 2) - 1, 1), 5);
  4303. }
  4304. // Get the path
  4305. pathResult = connection.getPath(options);
  4306. path = pathResult.path;
  4307. // Always update obstacle storage with obstacles from this path.
  4308. // We don't know if future calls will need this for their algorithm.
  4309. if (pathResult.obstacles) {
  4310. pathfinder.lineObstacles =
  4311. pathfinder.lineObstacles || [];
  4312. pathfinder.lineObstacles =
  4313. pathfinder.lineObstacles.concat(pathResult.obstacles);
  4314. }
  4315. // Add the calculated path to the pathfinder group
  4316. connection.renderPath(path, attribs, series.options.animation);
  4317. // Render the markers
  4318. connection.addMarker('start', merge(options.marker, options.startMarker), path);
  4319. connection.addMarker('end', merge(options.marker, options.endMarker), path);
  4320. },
  4321. /**
  4322. * Destroy connection by destroying the added graphics elements.
  4323. *
  4324. * @function Highcharts.Connection#destroy
  4325. */
  4326. destroy: function () {
  4327. if (this.graphics) {
  4328. objectEach(this.graphics, function (val) {
  4329. val.destroy();
  4330. });
  4331. delete this.graphics;
  4332. }
  4333. }
  4334. };
  4335. /**
  4336. * The Pathfinder class.
  4337. *
  4338. * @private
  4339. * @class
  4340. * @name Highcharts.Pathfinder
  4341. *
  4342. * @param {Highcharts.Chart} chart
  4343. * The chart to operate on.
  4344. */
  4345. function Pathfinder(chart) {
  4346. this.init(chart);
  4347. }
  4348. Pathfinder.prototype = {
  4349. /**
  4350. * @name Highcharts.Pathfinder#algorithms
  4351. * @type {Highcharts.Dictionary<Function>}
  4352. */
  4353. algorithms: pathfinderAlgorithms,
  4354. /**
  4355. * Initialize the Pathfinder object.
  4356. *
  4357. * @function Highcharts.Pathfinder#init
  4358. *
  4359. * @param {Highcharts.Chart} chart
  4360. * The chart context.
  4361. */
  4362. init: function (chart) {
  4363. // Initialize pathfinder with chart context
  4364. this.chart = chart;
  4365. // Init connection reference list
  4366. this.connections = [];
  4367. // Recalculate paths/obstacles on chart redraw
  4368. addEvent(chart, 'redraw', function () {
  4369. this.pathfinder.update();
  4370. });
  4371. },
  4372. /**
  4373. * Update Pathfinder connections from scratch.
  4374. *
  4375. * @function Highcharts.Pathfinder#update
  4376. *
  4377. * @param {boolean} [deferRender]
  4378. * Whether or not to defer rendering of connections until
  4379. * series.afterAnimate event has fired. Used on first render.
  4380. */
  4381. update: function (deferRender) {
  4382. var chart = this.chart, pathfinder = this, oldConnections = pathfinder.connections;
  4383. // Rebuild pathfinder connections from options
  4384. pathfinder.connections = [];
  4385. chart.series.forEach(function (series) {
  4386. if (series.visible && !series.options.isInternal) {
  4387. series.points.forEach(function (point) {
  4388. var to, connects = (point.options &&
  4389. point.options.connect &&
  4390. splat(point.options.connect));
  4391. if (point.visible && point.isInside !== false && connects) {
  4392. connects.forEach(function (connect) {
  4393. to = chart.get(typeof connect === 'string' ?
  4394. connect : connect.to);
  4395. if (to instanceof Point &&
  4396. to.series.visible &&
  4397. to.visible &&
  4398. to.isInside !== false) {
  4399. // Add new connection
  4400. pathfinder.connections.push(new Connection(point, // from
  4401. to, typeof connect === 'string' ?
  4402. {} :
  4403. connect));
  4404. }
  4405. });
  4406. }
  4407. });
  4408. }
  4409. });
  4410. // Clear connections that should not be updated, and move old info over
  4411. // to new connections.
  4412. for (var j = 0, k, found, lenOld = oldConnections.length, lenNew = pathfinder.connections.length; j < lenOld; ++j) {
  4413. found = false;
  4414. for (k = 0; k < lenNew; ++k) {
  4415. if (oldConnections[j].fromPoint ===
  4416. pathfinder.connections[k].fromPoint &&
  4417. oldConnections[j].toPoint ===
  4418. pathfinder.connections[k].toPoint) {
  4419. pathfinder.connections[k].graphics =
  4420. oldConnections[j].graphics;
  4421. found = true;
  4422. break;
  4423. }
  4424. }
  4425. if (!found) {
  4426. oldConnections[j].destroy();
  4427. }
  4428. }
  4429. // Clear obstacles to force recalculation. This must be done on every
  4430. // redraw in case positions have changed. Recalculation is handled in
  4431. // Connection.getPath on demand.
  4432. delete this.chartObstacles;
  4433. delete this.lineObstacles;
  4434. // Draw the pending connections
  4435. pathfinder.renderConnections(deferRender);
  4436. },
  4437. /**
  4438. * Draw the chart's connecting paths.
  4439. *
  4440. * @function Highcharts.Pathfinder#renderConnections
  4441. *
  4442. * @param {boolean} [deferRender]
  4443. * Whether or not to defer render until series animation is finished.
  4444. * Used on first render.
  4445. */
  4446. renderConnections: function (deferRender) {
  4447. if (deferRender) {
  4448. // Render after series are done animating
  4449. this.chart.series.forEach(function (series) {
  4450. var render = function () {
  4451. // Find pathfinder connections belonging to this series
  4452. // that haven't rendered, and render them now.
  4453. var pathfinder = series.chart.pathfinder, conns = pathfinder && pathfinder.connections || [];
  4454. conns.forEach(function (connection) {
  4455. if (connection.fromPoint &&
  4456. connection.fromPoint.series === series) {
  4457. connection.render();
  4458. }
  4459. });
  4460. if (series.pathfinderRemoveRenderEvent) {
  4461. series.pathfinderRemoveRenderEvent();
  4462. delete series.pathfinderRemoveRenderEvent;
  4463. }
  4464. };
  4465. if (series.options.animation === false) {
  4466. render();
  4467. }
  4468. else {
  4469. series.pathfinderRemoveRenderEvent = addEvent(series, 'afterAnimate', render);
  4470. }
  4471. });
  4472. }
  4473. else {
  4474. // Go through connections and render them
  4475. this.connections.forEach(function (connection) {
  4476. connection.render();
  4477. });
  4478. }
  4479. },
  4480. /**
  4481. * Get obstacles for the points in the chart. Does not include connecting
  4482. * lines from Pathfinder. Applies algorithmMargin to the obstacles.
  4483. *
  4484. * @function Highcharts.Pathfinder#getChartObstacles
  4485. *
  4486. * @param {object} options
  4487. * Options for the calculation. Currenlty only
  4488. * options.algorithmMargin.
  4489. *
  4490. * @return {Array<object>}
  4491. * An array of calculated obstacles. Each obstacle is defined as an
  4492. * object with xMin, xMax, yMin and yMax properties.
  4493. */
  4494. getChartObstacles: function (options) {
  4495. var obstacles = [], series = this.chart.series, margin = pick(options.algorithmMargin, 0), calculatedMargin;
  4496. for (var i = 0, sLen = series.length; i < sLen; ++i) {
  4497. if (series[i].visible && !series[i].options.isInternal) {
  4498. for (var j = 0, pLen = series[i].points.length, bb, point; j < pLen; ++j) {
  4499. point = series[i].points[j];
  4500. if (point.visible) {
  4501. bb = getPointBB(point);
  4502. if (bb) {
  4503. obstacles.push({
  4504. xMin: bb.xMin - margin,
  4505. xMax: bb.xMax + margin,
  4506. yMin: bb.yMin - margin,
  4507. yMax: bb.yMax + margin
  4508. });
  4509. }
  4510. }
  4511. }
  4512. }
  4513. }
  4514. // Sort obstacles by xMin for optimization
  4515. obstacles = obstacles.sort(function (a, b) {
  4516. return a.xMin - b.xMin;
  4517. });
  4518. // Add auto-calculated margin if the option is not defined
  4519. if (!defined(options.algorithmMargin)) {
  4520. calculatedMargin =
  4521. options.algorithmMargin =
  4522. calculateObstacleMargin(obstacles);
  4523. obstacles.forEach(function (obstacle) {
  4524. obstacle.xMin -= calculatedMargin;
  4525. obstacle.xMax += calculatedMargin;
  4526. obstacle.yMin -= calculatedMargin;
  4527. obstacle.yMax += calculatedMargin;
  4528. });
  4529. }
  4530. return obstacles;
  4531. },
  4532. /**
  4533. * Utility function to get metrics for obstacles:
  4534. * - Widest obstacle width
  4535. * - Tallest obstacle height
  4536. *
  4537. * @function Highcharts.Pathfinder#getObstacleMetrics
  4538. *
  4539. * @param {Array<object>} obstacles
  4540. * An array of obstacles to inspect.
  4541. *
  4542. * @return {object}
  4543. * The calculated metrics, as an object with maxHeight and maxWidth
  4544. * properties.
  4545. */
  4546. getObstacleMetrics: function (obstacles) {
  4547. var maxWidth = 0, maxHeight = 0, width, height, i = obstacles.length;
  4548. while (i--) {
  4549. width = obstacles[i].xMax - obstacles[i].xMin;
  4550. height = obstacles[i].yMax - obstacles[i].yMin;
  4551. if (maxWidth < width) {
  4552. maxWidth = width;
  4553. }
  4554. if (maxHeight < height) {
  4555. maxHeight = height;
  4556. }
  4557. }
  4558. return {
  4559. maxHeight: maxHeight,
  4560. maxWidth: maxWidth
  4561. };
  4562. },
  4563. /**
  4564. * Utility to get which direction to start the pathfinding algorithm
  4565. * (X vs Y), calculated from a set of marker options.
  4566. *
  4567. * @function Highcharts.Pathfinder#getAlgorithmStartDirection
  4568. *
  4569. * @param {Highcharts.ConnectorsMarkerOptions} markerOptions
  4570. * Marker options to calculate from.
  4571. *
  4572. * @return {boolean}
  4573. * Returns true for X, false for Y, and undefined for autocalculate.
  4574. */
  4575. getAlgorithmStartDirection: function (markerOptions) {
  4576. var xCenter = markerOptions.align !== 'left' &&
  4577. markerOptions.align !== 'right', yCenter = markerOptions.verticalAlign !== 'top' &&
  4578. markerOptions.verticalAlign !== 'bottom', undef;
  4579. return xCenter ?
  4580. (yCenter ? undef : false) : // x is centered
  4581. (yCenter ? true : undef); // x is off-center
  4582. }
  4583. };
  4584. // Add to Highcharts namespace
  4585. H.Connection = Connection;
  4586. H.Pathfinder = Pathfinder;
  4587. // Add pathfinding capabilities to Points
  4588. extend(Point.prototype, /** @lends Point.prototype */ {
  4589. /**
  4590. * Get coordinates of anchor point for pathfinder connection.
  4591. *
  4592. * @private
  4593. * @function Highcharts.Point#getPathfinderAnchorPoint
  4594. *
  4595. * @param {Highcharts.ConnectorsMarkerOptions} markerOptions
  4596. * Connection options for position on point.
  4597. *
  4598. * @return {Highcharts.PositionObject}
  4599. * An object with x/y properties for the position. Coordinates are
  4600. * in plot values, not relative to point.
  4601. */
  4602. getPathfinderAnchorPoint: function (markerOptions) {
  4603. var bb = getPointBB(this), x, y;
  4604. switch (markerOptions.align) { // eslint-disable-line default-case
  4605. case 'right':
  4606. x = 'xMax';
  4607. break;
  4608. case 'left':
  4609. x = 'xMin';
  4610. }
  4611. switch (markerOptions.verticalAlign) { // eslint-disable-line default-case
  4612. case 'top':
  4613. y = 'yMin';
  4614. break;
  4615. case 'bottom':
  4616. y = 'yMax';
  4617. }
  4618. return {
  4619. x: x ? bb[x] : (bb.xMin + bb.xMax) / 2,
  4620. y: y ? bb[y] : (bb.yMin + bb.yMax) / 2
  4621. };
  4622. },
  4623. /**
  4624. * Utility to get the angle from one point to another.
  4625. *
  4626. * @private
  4627. * @function Highcharts.Point#getRadiansToVector
  4628. *
  4629. * @param {Highcharts.PositionObject} v1
  4630. * The first vector, as an object with x/y properties.
  4631. *
  4632. * @param {Highcharts.PositionObject} v2
  4633. * The second vector, as an object with x/y properties.
  4634. *
  4635. * @return {number}
  4636. * The angle in degrees
  4637. */
  4638. getRadiansToVector: function (v1, v2) {
  4639. var box;
  4640. if (!defined(v2)) {
  4641. box = getPointBB(this);
  4642. if (box) {
  4643. v2 = {
  4644. x: (box.xMin + box.xMax) / 2,
  4645. y: (box.yMin + box.yMax) / 2
  4646. };
  4647. }
  4648. }
  4649. return Math.atan2(v2.y - v1.y, v1.x - v2.x);
  4650. },
  4651. /**
  4652. * Utility to get the position of the marker, based on the path angle and
  4653. * the marker's radius.
  4654. *
  4655. * @private
  4656. * @function Highcharts.Point#getMarkerVector
  4657. *
  4658. * @param {number} radians
  4659. * The angle in radians from the point center to another vector.
  4660. *
  4661. * @param {number} markerRadius
  4662. * The radius of the marker, to calculate the additional distance to
  4663. * the center of the marker.
  4664. *
  4665. * @param {object} anchor
  4666. * The anchor point of the path and marker as an object with x/y
  4667. * properties.
  4668. *
  4669. * @return {object}
  4670. * The marker vector as an object with x/y properties.
  4671. */
  4672. getMarkerVector: function (radians, markerRadius, anchor) {
  4673. var twoPI = Math.PI * 2.0, theta = radians, bb = getPointBB(this), rectWidth = bb.xMax - bb.xMin, rectHeight = bb.yMax - bb.yMin, rAtan = Math.atan2(rectHeight, rectWidth), tanTheta = 1, leftOrRightRegion = false, rectHalfWidth = rectWidth / 2.0, rectHalfHeight = rectHeight / 2.0, rectHorizontalCenter = bb.xMin + rectHalfWidth, rectVerticalCenter = bb.yMin + rectHalfHeight, edgePoint = {
  4674. x: rectHorizontalCenter,
  4675. y: rectVerticalCenter
  4676. }, markerPoint = {}, xFactor = 1, yFactor = 1;
  4677. while (theta < -Math.PI) {
  4678. theta += twoPI;
  4679. }
  4680. while (theta > Math.PI) {
  4681. theta -= twoPI;
  4682. }
  4683. tanTheta = Math.tan(theta);
  4684. if ((theta > -rAtan) && (theta <= rAtan)) {
  4685. // Right side
  4686. yFactor = -1;
  4687. leftOrRightRegion = true;
  4688. }
  4689. else if (theta > rAtan && theta <= (Math.PI - rAtan)) {
  4690. // Top side
  4691. yFactor = -1;
  4692. }
  4693. else if (theta > (Math.PI - rAtan) || theta <= -(Math.PI - rAtan)) {
  4694. // Left side
  4695. xFactor = -1;
  4696. leftOrRightRegion = true;
  4697. }
  4698. else {
  4699. // Bottom side
  4700. xFactor = -1;
  4701. }
  4702. // Correct the edgePoint according to the placement of the marker
  4703. if (leftOrRightRegion) {
  4704. edgePoint.x += xFactor * (rectHalfWidth);
  4705. edgePoint.y += yFactor * (rectHalfWidth) * tanTheta;
  4706. }
  4707. else {
  4708. edgePoint.x += xFactor * (rectHeight / (2.0 * tanTheta));
  4709. edgePoint.y += yFactor * (rectHalfHeight);
  4710. }
  4711. if (anchor.x !== rectHorizontalCenter) {
  4712. edgePoint.x = anchor.x;
  4713. }
  4714. if (anchor.y !== rectVerticalCenter) {
  4715. edgePoint.y = anchor.y;
  4716. }
  4717. markerPoint.x = edgePoint.x + (markerRadius * Math.cos(theta));
  4718. markerPoint.y = edgePoint.y - (markerRadius * Math.sin(theta));
  4719. return markerPoint;
  4720. }
  4721. });
  4722. /**
  4723. * Warn if using legacy options. Copy the options over. Note that this will
  4724. * still break if using the legacy options in chart.update, addSeries etc.
  4725. * @private
  4726. */
  4727. function warnLegacy(chart) {
  4728. if (chart.options.pathfinder ||
  4729. chart.series.reduce(function (acc, series) {
  4730. if (series.options) {
  4731. merge(true, (series.options.connectors = series.options.connectors ||
  4732. {}), series.options.pathfinder);
  4733. }
  4734. return acc || series.options && series.options.pathfinder;
  4735. }, false)) {
  4736. merge(true, (chart.options.connectors = chart.options.connectors || {}), chart.options.pathfinder);
  4737. error('WARNING: Pathfinder options have been renamed. ' +
  4738. 'Use "chart.connectors" or "series.connectors" instead.');
  4739. }
  4740. }
  4741. // Initialize Pathfinder for charts
  4742. Chart.prototype.callbacks.push(function (chart) {
  4743. var options = chart.options;
  4744. if (options.connectors.enabled !== false) {
  4745. warnLegacy(chart);
  4746. this.pathfinder = new Pathfinder(this);
  4747. this.pathfinder.update(true); // First draw, defer render
  4748. }
  4749. });
  4750. });
  4751. _registerModule(_modules, 'modules/xrange.src.js', [_modules['parts/Axis.js'], _modules['parts/Globals.js'], _modules['parts/Color.js'], _modules['parts/Point.js'], _modules['parts/Utilities.js']], function (Axis, H, Color, Point, U) {
  4752. /* *
  4753. *
  4754. * X-range series module
  4755. *
  4756. * (c) 2010-2020 Torstein Honsi, Lars A. V. Cabrera
  4757. *
  4758. * License: www.highcharts.com/license
  4759. *
  4760. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  4761. *
  4762. * */
  4763. var color = Color.parse;
  4764. var addEvent = U.addEvent, clamp = U.clamp, correctFloat = U.correctFloat, defined = U.defined, find = U.find, isNumber = U.isNumber, isObject = U.isObject, merge = U.merge, pick = U.pick, seriesType = U.seriesType;
  4765. /* *
  4766. * @interface Highcharts.PointOptionsObject in parts/Point.ts
  4767. */ /**
  4768. * The ending X value of the range point.
  4769. * @name Highcharts.PointOptionsObject#x2
  4770. * @type {number|undefined}
  4771. * @requires modules/xrange
  4772. */
  4773. var columnType = H.seriesTypes.column, seriesTypes = H.seriesTypes, Series = H.Series;
  4774. /**
  4775. * Return color of a point based on its category.
  4776. *
  4777. * @private
  4778. * @function getColorByCategory
  4779. *
  4780. * @param {object} series
  4781. * The series which the point belongs to.
  4782. *
  4783. * @param {object} point
  4784. * The point to calculate its color for.
  4785. *
  4786. * @return {object}
  4787. * Returns an object containing the properties color and colorIndex.
  4788. */
  4789. function getColorByCategory(series, point) {
  4790. var colors = series.options.colors || series.chart.options.colors, colorCount = colors ?
  4791. colors.length :
  4792. series.chart.options.chart.colorCount, colorIndex = point.y % colorCount, color = colors && colors[colorIndex];
  4793. return {
  4794. colorIndex: colorIndex,
  4795. color: color
  4796. };
  4797. }
  4798. /**
  4799. * @private
  4800. * @class
  4801. * @name Highcharts.seriesTypes.xrange
  4802. *
  4803. * @augments Highcharts.Series
  4804. */
  4805. seriesType('xrange', 'column'
  4806. /**
  4807. * The X-range series displays ranges on the X axis, typically time
  4808. * intervals with a start and end date.
  4809. *
  4810. * @sample {highcharts} highcharts/demo/x-range/
  4811. * X-range
  4812. * @sample {highcharts} highcharts/css/x-range/
  4813. * Styled mode X-range
  4814. * @sample {highcharts} highcharts/chart/inverted-xrange/
  4815. * Inverted X-range
  4816. *
  4817. * @extends plotOptions.column
  4818. * @since 6.0.0
  4819. * @product highcharts highstock gantt
  4820. * @excluding boostThreshold, crisp, cropThreshold, depth, edgeColor,
  4821. * edgeWidth, findNearestPointBy, getExtremesFromAll,
  4822. * negativeColor, pointInterval, pointIntervalUnit,
  4823. * pointPlacement, pointRange, pointStart, softThreshold,
  4824. * stacking, threshold, data, dataSorting
  4825. * @requires modules/xrange
  4826. * @optionparent plotOptions.xrange
  4827. */
  4828. , {
  4829. /**
  4830. * A partial fill for each point, typically used to visualize how much
  4831. * of a task is performed. The partial fill object can be set either on
  4832. * series or point level.
  4833. *
  4834. * @sample {highcharts} highcharts/demo/x-range
  4835. * X-range with partial fill
  4836. *
  4837. * @product highcharts highstock gantt
  4838. * @apioption plotOptions.xrange.partialFill
  4839. */
  4840. /**
  4841. * The fill color to be used for partial fills. Defaults to a darker
  4842. * shade of the point color.
  4843. *
  4844. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  4845. * @product highcharts highstock gantt
  4846. * @apioption plotOptions.xrange.partialFill.fill
  4847. */
  4848. /**
  4849. * A partial fill for each point, typically used to visualize how much
  4850. * of a task is performed. See [completed](series.gantt.data.completed).
  4851. *
  4852. * @sample gantt/demo/progress-indicator
  4853. * Gantt with progress indicator
  4854. *
  4855. * @product gantt
  4856. * @apioption plotOptions.gantt.partialFill
  4857. */
  4858. /**
  4859. * In an X-range series, this option makes all points of the same Y-axis
  4860. * category the same color.
  4861. */
  4862. colorByPoint: true,
  4863. dataLabels: {
  4864. formatter: function () {
  4865. var point = this.point, amount = point.partialFill;
  4866. if (isObject(amount)) {
  4867. amount = amount.amount;
  4868. }
  4869. if (isNumber(amount) && amount > 0) {
  4870. return correctFloat(amount * 100) + '%';
  4871. }
  4872. },
  4873. inside: true,
  4874. verticalAlign: 'middle'
  4875. },
  4876. tooltip: {
  4877. headerFormat: '<span style="font-size: 10px">{point.x} - {point.x2}</span><br/>',
  4878. pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.yCategory}</b><br/>'
  4879. },
  4880. borderRadius: 3,
  4881. pointRange: 0
  4882. }, {
  4883. type: 'xrange',
  4884. parallelArrays: ['x', 'x2', 'y'],
  4885. requireSorting: false,
  4886. animate: seriesTypes.line.prototype.animate,
  4887. cropShoulder: 1,
  4888. getExtremesFromAll: true,
  4889. autoIncrement: H.noop,
  4890. buildKDTree: H.noop,
  4891. /* eslint-disable valid-jsdoc */
  4892. /**
  4893. * @private
  4894. * @function Highcarts.seriesTypes.xrange#init
  4895. * @return {void}
  4896. */
  4897. init: function () {
  4898. seriesTypes.column.prototype.init.apply(this, arguments);
  4899. this.options.stacking = void 0; // #13161
  4900. },
  4901. /**
  4902. * Borrow the column series metrics, but with swapped axes. This gives
  4903. * free access to features like groupPadding, grouping, pointWidth etc.
  4904. *
  4905. * @private
  4906. * @function Highcharts.Series#getColumnMetrics
  4907. *
  4908. * @return {Highcharts.ColumnMetricsObject}
  4909. */
  4910. getColumnMetrics: function () {
  4911. var metrics, chart = this.chart;
  4912. /**
  4913. * @private
  4914. */
  4915. function swapAxes() {
  4916. chart.series.forEach(function (s) {
  4917. var xAxis = s.xAxis;
  4918. s.xAxis = s.yAxis;
  4919. s.yAxis = xAxis;
  4920. });
  4921. }
  4922. swapAxes();
  4923. metrics = columnType.prototype.getColumnMetrics.call(this);
  4924. swapAxes();
  4925. return metrics;
  4926. },
  4927. /**
  4928. * Override cropData to show a point where x or x2 is outside visible
  4929. * range, but one of them is inside.
  4930. *
  4931. * @private
  4932. * @function Highcharts.Series#cropData
  4933. *
  4934. * @param {Array<number>} xData
  4935. *
  4936. * @param {Array<number>} yData
  4937. *
  4938. * @param {number} min
  4939. *
  4940. * @param {number} max
  4941. *
  4942. * @param {number} [cropShoulder]
  4943. *
  4944. * @return {*}
  4945. */
  4946. cropData: function (xData, yData, min, max) {
  4947. // Replace xData with x2Data to find the appropriate cropStart
  4948. var cropData = Series.prototype.cropData, crop = cropData.call(this, this.x2Data, yData, min, max);
  4949. // Re-insert the cropped xData
  4950. crop.xData = xData.slice(crop.start, crop.end);
  4951. return crop;
  4952. },
  4953. /**
  4954. * Finds the index of an existing point that matches the given point
  4955. * options.
  4956. *
  4957. * @private
  4958. * @function Highcharts.Series#findPointIndex
  4959. * @param {object} options The options of the point.
  4960. * @returns {number|undefined} Returns index of a matching point,
  4961. * returns undefined if no match is found.
  4962. */
  4963. findPointIndex: function (options) {
  4964. var _a = this, cropped = _a.cropped, cropStart = _a.cropStart, points = _a.points;
  4965. var id = options.id;
  4966. var pointIndex;
  4967. if (id) {
  4968. var point = find(points, function (point) {
  4969. return point.id === id;
  4970. });
  4971. pointIndex = point ? point.index : void 0;
  4972. }
  4973. if (typeof pointIndex === 'undefined') {
  4974. var point = find(points, function (point) {
  4975. return (point.x === options.x &&
  4976. point.x2 === options.x2 &&
  4977. !point.touched);
  4978. });
  4979. pointIndex = point ? point.index : void 0;
  4980. }
  4981. // Reduce pointIndex if data is cropped
  4982. if (cropped &&
  4983. isNumber(pointIndex) &&
  4984. isNumber(cropStart) &&
  4985. pointIndex >= cropStart) {
  4986. pointIndex -= cropStart;
  4987. }
  4988. return pointIndex;
  4989. },
  4990. /**
  4991. * @private
  4992. * @function Highcharts.Series#translatePoint
  4993. *
  4994. * @param {Highcharts.Point} point
  4995. */
  4996. translatePoint: function (point) {
  4997. var series = this, xAxis = series.xAxis, yAxis = series.yAxis, metrics = series.columnMetrics, options = series.options, minPointLength = options.minPointLength || 0, plotX = point.plotX, posX = pick(point.x2, point.x + (point.len || 0)), plotX2 = xAxis.translate(posX, 0, 0, 0, 1), length = Math.abs(plotX2 - plotX), widthDifference, shapeArgs, partialFill, inverted = this.chart.inverted, borderWidth = pick(options.borderWidth, 1), crisper = borderWidth % 2 / 2, yOffset = metrics.offset, pointHeight = Math.round(metrics.width), dlLeft, dlRight, dlWidth, clipRectWidth, tooltipYOffset;
  4998. if (minPointLength) {
  4999. widthDifference = minPointLength - length;
  5000. if (widthDifference < 0) {
  5001. widthDifference = 0;
  5002. }
  5003. plotX -= widthDifference / 2;
  5004. plotX2 += widthDifference / 2;
  5005. }
  5006. plotX = Math.max(plotX, -10);
  5007. plotX2 = clamp(plotX2, -10, xAxis.len + 10);
  5008. // Handle individual pointWidth
  5009. if (defined(point.options.pointWidth)) {
  5010. yOffset -= ((Math.ceil(point.options.pointWidth) - pointHeight) / 2);
  5011. pointHeight = Math.ceil(point.options.pointWidth);
  5012. }
  5013. // Apply pointPlacement to the Y axis
  5014. if (options.pointPlacement &&
  5015. isNumber(point.plotY) &&
  5016. yAxis.categories) {
  5017. point.plotY = yAxis.translate(point.y, 0, 1, 0, 1, options.pointPlacement);
  5018. }
  5019. point.shapeArgs = {
  5020. x: Math.floor(Math.min(plotX, plotX2)) + crisper,
  5021. y: Math.floor(point.plotY + yOffset) + crisper,
  5022. width: Math.round(Math.abs(plotX2 - plotX)),
  5023. height: pointHeight,
  5024. r: series.options.borderRadius
  5025. };
  5026. // Align data labels inside the shape and inside the plot area
  5027. dlLeft = point.shapeArgs.x;
  5028. dlRight = dlLeft + point.shapeArgs.width;
  5029. if (dlLeft < 0 || dlRight > xAxis.len) {
  5030. dlLeft = clamp(dlLeft, 0, xAxis.len);
  5031. dlRight = clamp(dlRight, 0, xAxis.len);
  5032. dlWidth = dlRight - dlLeft;
  5033. point.dlBox = merge(point.shapeArgs, {
  5034. x: dlLeft,
  5035. width: dlRight - dlLeft,
  5036. centerX: dlWidth ? dlWidth / 2 : null
  5037. });
  5038. }
  5039. else {
  5040. point.dlBox = null;
  5041. }
  5042. // Tooltip position
  5043. var tooltipPos = point.tooltipPos;
  5044. var xIndex = !inverted ? 0 : 1;
  5045. var yIndex = !inverted ? 1 : 0;
  5046. tooltipYOffset = series.columnMetrics ?
  5047. series.columnMetrics.offset : -metrics.width / 2;
  5048. // Limit position by the correct axis size (#9727)
  5049. tooltipPos[xIndex] = clamp(tooltipPos[xIndex] + ((!inverted ? 1 : -1) * (xAxis.reversed ? -1 : 1) *
  5050. (length / 2)), 0, xAxis.len - 1);
  5051. tooltipPos[yIndex] = clamp(tooltipPos[yIndex] + ((inverted ? -1 : 1) * tooltipYOffset), 0, yAxis.len - 1);
  5052. // Add a partShapeArgs to the point, based on the shapeArgs property
  5053. partialFill = point.partialFill;
  5054. if (partialFill) {
  5055. // Get the partial fill amount
  5056. if (isObject(partialFill)) {
  5057. partialFill = partialFill.amount;
  5058. }
  5059. // If it was not a number, assume 0
  5060. if (!isNumber(partialFill)) {
  5061. partialFill = 0;
  5062. }
  5063. shapeArgs = point.shapeArgs;
  5064. point.partShapeArgs = {
  5065. x: shapeArgs.x,
  5066. y: shapeArgs.y,
  5067. width: shapeArgs.width,
  5068. height: shapeArgs.height,
  5069. r: series.options.borderRadius
  5070. };
  5071. clipRectWidth = Math.max(Math.round(length * partialFill + point.plotX -
  5072. plotX), 0);
  5073. point.clipRectArgs = {
  5074. x: xAxis.reversed ? // #10717
  5075. shapeArgs.x + length - clipRectWidth :
  5076. shapeArgs.x,
  5077. y: shapeArgs.y,
  5078. width: clipRectWidth,
  5079. height: shapeArgs.height
  5080. };
  5081. }
  5082. },
  5083. /**
  5084. * @private
  5085. * @function Highcharts.Series#translate
  5086. */
  5087. translate: function () {
  5088. columnType.prototype.translate.apply(this, arguments);
  5089. this.points.forEach(function (point) {
  5090. this.translatePoint(point);
  5091. }, this);
  5092. },
  5093. /**
  5094. * Draws a single point in the series. Needed for partial fill.
  5095. *
  5096. * This override turns point.graphic into a group containing the
  5097. * original graphic and an overlay displaying the partial fill.
  5098. *
  5099. * @private
  5100. * @function Highcharts.Series#drawPoint
  5101. *
  5102. * @param {Highcharts.Point} point
  5103. * An instance of Point in the series.
  5104. *
  5105. * @param {"animate"|"attr"} verb
  5106. * 'animate' (animates changes) or 'attr' (sets options)
  5107. */
  5108. drawPoint: function (point, verb) {
  5109. var series = this, seriesOpts = series.options, renderer = series.chart.renderer, graphic = point.graphic, type = point.shapeType, shapeArgs = point.shapeArgs, partShapeArgs = point.partShapeArgs, clipRectArgs = point.clipRectArgs, pfOptions = point.partialFill, cutOff = seriesOpts.stacking && !seriesOpts.borderRadius, pointState = point.state, stateOpts = (seriesOpts.states[pointState || 'normal'] ||
  5110. {}), pointStateVerb = typeof pointState === 'undefined' ?
  5111. 'attr' : verb, pointAttr = series.pointAttribs(point, pointState), animation = pick(series.chart.options.chart.animation, stateOpts.animation), fill;
  5112. if (!point.isNull && point.visible !== false) {
  5113. // Original graphic
  5114. if (graphic) { // update
  5115. graphic.rect[verb](shapeArgs);
  5116. }
  5117. else {
  5118. point.graphic = graphic = renderer.g('point')
  5119. .addClass(point.getClassName())
  5120. .add(point.group || series.group);
  5121. graphic.rect = renderer[type](merge(shapeArgs))
  5122. .addClass(point.getClassName())
  5123. .addClass('highcharts-partfill-original')
  5124. .add(graphic);
  5125. }
  5126. // Partial fill graphic
  5127. if (partShapeArgs) {
  5128. if (graphic.partRect) {
  5129. graphic.partRect[verb](merge(partShapeArgs));
  5130. graphic.partialClipRect[verb](merge(clipRectArgs));
  5131. }
  5132. else {
  5133. graphic.partialClipRect = renderer.clipRect(clipRectArgs.x, clipRectArgs.y, clipRectArgs.width, clipRectArgs.height);
  5134. graphic.partRect =
  5135. renderer[type](partShapeArgs)
  5136. .addClass('highcharts-partfill-overlay')
  5137. .add(graphic)
  5138. .clip(graphic.partialClipRect);
  5139. }
  5140. }
  5141. // Presentational
  5142. if (!series.chart.styledMode) {
  5143. graphic
  5144. .rect[verb](pointAttr, animation)
  5145. .shadow(seriesOpts.shadow, null, cutOff);
  5146. if (partShapeArgs) {
  5147. // Ensure pfOptions is an object
  5148. if (!isObject(pfOptions)) {
  5149. pfOptions = {};
  5150. }
  5151. if (isObject(seriesOpts.partialFill)) {
  5152. pfOptions = merge(pfOptions, seriesOpts.partialFill);
  5153. }
  5154. fill = (pfOptions.fill ||
  5155. color(pointAttr.fill).brighten(-0.3).get() ||
  5156. color(point.color || series.color)
  5157. .brighten(-0.3).get());
  5158. pointAttr.fill = fill;
  5159. graphic
  5160. .partRect[pointStateVerb](pointAttr, animation)
  5161. .shadow(seriesOpts.shadow, null, cutOff);
  5162. }
  5163. }
  5164. }
  5165. else if (graphic) {
  5166. point.graphic = graphic.destroy(); // #1269
  5167. }
  5168. },
  5169. /**
  5170. * @private
  5171. * @function Highcharts.Series#drawPoints
  5172. */
  5173. drawPoints: function () {
  5174. var series = this, verb = series.getAnimationVerb();
  5175. // Draw the columns
  5176. series.points.forEach(function (point) {
  5177. series.drawPoint(point, verb);
  5178. });
  5179. },
  5180. /**
  5181. * Returns "animate", or "attr" if the number of points is above the
  5182. * animation limit.
  5183. *
  5184. * @private
  5185. * @function Highcharts.Series#getAnimationVerb
  5186. *
  5187. * @return {string}
  5188. */
  5189. getAnimationVerb: function () {
  5190. return (this.chart.pointCount < (this.options.animationLimit || 250) ?
  5191. 'animate' :
  5192. 'attr');
  5193. }
  5194. /*
  5195. // Override to remove stroke from points. For partial fill.
  5196. pointAttribs: function () {
  5197. var series = this,
  5198. retVal = columnType.prototype.pointAttribs
  5199. .apply(series, arguments);
  5200. //retVal['stroke-width'] = 0;
  5201. return retVal;
  5202. }
  5203. //*/
  5204. /* eslint-enable valid-jsdoc */
  5205. }, {
  5206. /**
  5207. * The ending X value of the range point.
  5208. * @name Highcharts.Point#x2
  5209. * @type {number|undefined}
  5210. * @requires modules/xrange
  5211. */
  5212. /**
  5213. * Extend applyOptions so that `colorByPoint` for x-range means that one
  5214. * color is applied per Y axis category.
  5215. *
  5216. * @private
  5217. * @function Highcharts.Point#applyOptions
  5218. *
  5219. * @return {Highcharts.Series}
  5220. */
  5221. /* eslint-disable valid-jsdoc */
  5222. /**
  5223. * @private
  5224. */
  5225. resolveColor: function () {
  5226. var series = this.series, colorByPoint;
  5227. if (series.options.colorByPoint && !this.options.color) {
  5228. colorByPoint = getColorByCategory(series, this);
  5229. if (!series.chart.styledMode) {
  5230. this.color = colorByPoint.color;
  5231. }
  5232. if (!this.options.colorIndex) {
  5233. this.colorIndex = colorByPoint.colorIndex;
  5234. }
  5235. }
  5236. else if (!this.color) {
  5237. this.color = series.color;
  5238. }
  5239. },
  5240. /**
  5241. * Extend init to have y default to 0.
  5242. *
  5243. * @private
  5244. * @function Highcharts.Point#init
  5245. *
  5246. * @return {Highcharts.Point}
  5247. */
  5248. init: function () {
  5249. Point.prototype.init.apply(this, arguments);
  5250. if (!this.y) {
  5251. this.y = 0;
  5252. }
  5253. return this;
  5254. },
  5255. /**
  5256. * @private
  5257. * @function Highcharts.Point#setState
  5258. */
  5259. setState: function () {
  5260. Point.prototype.setState.apply(this, arguments);
  5261. this.series.drawPoint(this, this.series.getAnimationVerb());
  5262. },
  5263. /**
  5264. * @private
  5265. * @function Highcharts.Point#getLabelConfig
  5266. *
  5267. * @return {Highcharts.PointLabelObject}
  5268. */
  5269. // Add x2 and yCategory to the available properties for tooltip formats
  5270. getLabelConfig: function () {
  5271. var point = this, cfg = Point.prototype.getLabelConfig.call(point), yCats = point.series.yAxis.categories;
  5272. cfg.x2 = point.x2;
  5273. cfg.yCategory = point.yCategory = yCats && yCats[point.y];
  5274. return cfg;
  5275. },
  5276. tooltipDateKeys: ['x', 'x2'],
  5277. /**
  5278. * @private
  5279. * @function Highcharts.Point#isValid
  5280. *
  5281. * @return {boolean}
  5282. */
  5283. isValid: function () {
  5284. return typeof this.x === 'number' &&
  5285. typeof this.x2 === 'number';
  5286. }
  5287. /* eslint-enable valid-jsdoc */
  5288. });
  5289. /**
  5290. * Max x2 should be considered in xAxis extremes
  5291. */
  5292. addEvent(Axis, 'afterGetSeriesExtremes', function () {
  5293. var axis = this, // eslint-disable-line no-invalid-this
  5294. axisSeries = axis.series, dataMax, modMax;
  5295. if (axis.isXAxis) {
  5296. dataMax = pick(axis.dataMax, -Number.MAX_VALUE);
  5297. axisSeries.forEach(function (series) {
  5298. if (series.x2Data) {
  5299. series.x2Data
  5300. .forEach(function (val) {
  5301. if (val > dataMax) {
  5302. dataMax = val;
  5303. modMax = true;
  5304. }
  5305. });
  5306. }
  5307. });
  5308. if (modMax) {
  5309. axis.dataMax = dataMax;
  5310. }
  5311. }
  5312. });
  5313. /**
  5314. * An `xrange` series. If the [type](#series.xrange.type) option is not
  5315. * specified, it is inherited from [chart.type](#chart.type).
  5316. *
  5317. * @extends series,plotOptions.xrange
  5318. * @excluding boostThreshold, crisp, cropThreshold, depth, edgeColor, edgeWidth,
  5319. * findNearestPointBy, getExtremesFromAll, negativeColor,
  5320. * pointInterval, pointIntervalUnit, pointPlacement, pointRange,
  5321. * pointStart, softThreshold, stacking, threshold, dataSorting
  5322. * @product highcharts highstock gantt
  5323. * @requires modules/xrange
  5324. * @apioption series.xrange
  5325. */
  5326. /**
  5327. * An array of data points for the series. For the `xrange` series type,
  5328. * points can be given in the following ways:
  5329. *
  5330. * 1. An array of objects with named values. The objects are point configuration
  5331. * objects as seen below.
  5332. * ```js
  5333. * data: [{
  5334. * x: Date.UTC(2017, 0, 1),
  5335. * x2: Date.UTC(2017, 0, 3),
  5336. * name: "Test",
  5337. * y: 0,
  5338. * color: "#00FF00"
  5339. * }, {
  5340. * x: Date.UTC(2017, 0, 4),
  5341. * x2: Date.UTC(2017, 0, 5),
  5342. * name: "Deploy",
  5343. * y: 1,
  5344. * color: "#FF0000"
  5345. * }]
  5346. * ```
  5347. *
  5348. * @sample {highcharts} highcharts/series/data-array-of-objects/
  5349. * Config objects
  5350. *
  5351. * @declare Highcharts.XrangePointOptionsObject
  5352. * @type {Array<*>}
  5353. * @extends series.line.data
  5354. * @product highcharts highstock gantt
  5355. * @apioption series.xrange.data
  5356. */
  5357. /**
  5358. * The starting X value of the range point.
  5359. *
  5360. * @sample {highcharts} highcharts/demo/x-range
  5361. * X-range
  5362. *
  5363. * @type {number}
  5364. * @product highcharts highstock gantt
  5365. * @apioption series.xrange.data.x
  5366. */
  5367. /**
  5368. * The ending X value of the range point.
  5369. *
  5370. * @sample {highcharts} highcharts/demo/x-range
  5371. * X-range
  5372. *
  5373. * @type {number}
  5374. * @product highcharts highstock gantt
  5375. * @apioption series.xrange.data.x2
  5376. */
  5377. /**
  5378. * The Y value of the range point.
  5379. *
  5380. * @sample {highcharts} highcharts/demo/x-range
  5381. * X-range
  5382. *
  5383. * @type {number}
  5384. * @product highcharts highstock gantt
  5385. * @apioption series.xrange.data.y
  5386. */
  5387. /**
  5388. * A partial fill for each point, typically used to visualize how much of
  5389. * a task is performed. The partial fill object can be set either on series
  5390. * or point level.
  5391. *
  5392. * @sample {highcharts} highcharts/demo/x-range
  5393. * X-range with partial fill
  5394. *
  5395. * @declare Highcharts.XrangePointPartialFillOptionsObject
  5396. * @product highcharts highstock gantt
  5397. * @apioption series.xrange.data.partialFill
  5398. */
  5399. /**
  5400. * The amount of the X-range point to be filled. Values can be 0-1 and are
  5401. * converted to percentages in the default data label formatter.
  5402. *
  5403. * @type {number}
  5404. * @product highcharts highstock gantt
  5405. * @apioption series.xrange.data.partialFill.amount
  5406. */
  5407. /**
  5408. * The fill color to be used for partial fills. Defaults to a darker shade
  5409. * of the point color.
  5410. *
  5411. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  5412. * @product highcharts highstock gantt
  5413. * @apioption series.xrange.data.partialFill.fill
  5414. */
  5415. ''; // adds doclets above to transpiled file
  5416. });
  5417. _registerModule(_modules, 'parts-gantt/GanttSeries.js', [_modules['parts/Globals.js'], _modules['parts/Options.js'], _modules['parts/Utilities.js']], function (H, O, U) {
  5418. /* *
  5419. *
  5420. * (c) 2016-2020 Highsoft AS
  5421. *
  5422. * Author: Lars A. V. Cabrera
  5423. *
  5424. * License: www.highcharts.com/license
  5425. *
  5426. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  5427. *
  5428. * */
  5429. var dateFormat = O.dateFormat;
  5430. var isNumber = U.isNumber, merge = U.merge, pick = U.pick, seriesType = U.seriesType, splat = U.splat;
  5431. var seriesTypes = H.seriesTypes, Series = H.Series, parent = seriesTypes.xrange;
  5432. /**
  5433. * @private
  5434. * @class
  5435. * @name Highcharts.seriesTypes.gantt
  5436. *
  5437. * @augments Highcharts.Series
  5438. */
  5439. seriesType('gantt', 'xrange'
  5440. /**
  5441. * A `gantt` series. If the [type](#series.gantt.type) option is not specified,
  5442. * it is inherited from [chart.type](#chart.type).
  5443. *
  5444. * @extends plotOptions.xrange
  5445. * @product gantt
  5446. * @requires highcharts-gantt
  5447. * @optionparent plotOptions.gantt
  5448. */
  5449. , {
  5450. // options - default options merged with parent
  5451. grouping: false,
  5452. dataLabels: {
  5453. enabled: true
  5454. },
  5455. tooltip: {
  5456. headerFormat: '<span style="font-size: 10px">{series.name}</span><br/>',
  5457. pointFormat: null,
  5458. pointFormatter: function () {
  5459. var point = this, series = point.series, tooltip = series.chart.tooltip, xAxis = series.xAxis, formats = series.tooltipOptions.dateTimeLabelFormats, startOfWeek = xAxis.options.startOfWeek, ttOptions = series.tooltipOptions, format = ttOptions.xDateFormat, start, end, milestone = point.options.milestone, retVal = '<b>' + (point.name || point.yCategory) + '</b>';
  5460. if (ttOptions.pointFormat) {
  5461. return point.tooltipFormatter(ttOptions.pointFormat);
  5462. }
  5463. if (!format) {
  5464. format = splat(tooltip.getDateFormat(xAxis.closestPointRange, point.start, startOfWeek, formats))[0];
  5465. }
  5466. start = dateFormat(format, point.start);
  5467. end = dateFormat(format, point.end);
  5468. retVal += '<br/>';
  5469. if (!milestone) {
  5470. retVal += 'Start: ' + start + '<br/>';
  5471. retVal += 'End: ' + end + '<br/>';
  5472. }
  5473. else {
  5474. retVal += start + '<br/>';
  5475. }
  5476. return retVal;
  5477. }
  5478. },
  5479. connectors: {
  5480. type: 'simpleConnect',
  5481. /**
  5482. * @declare Highcharts.ConnectorsAnimationOptionsObject
  5483. */
  5484. animation: {
  5485. reversed: true // Dependencies go from child to parent
  5486. },
  5487. startMarker: {
  5488. enabled: true,
  5489. symbol: 'arrow-filled',
  5490. radius: 4,
  5491. fill: '#fa0',
  5492. align: 'left'
  5493. },
  5494. endMarker: {
  5495. enabled: false,
  5496. align: 'right'
  5497. }
  5498. }
  5499. }, {
  5500. pointArrayMap: ['start', 'end', 'y'],
  5501. // Keyboard navigation, don't use nearest vertical mode
  5502. keyboardMoveVertical: false,
  5503. /* eslint-disable valid-jsdoc */
  5504. /**
  5505. * Handle milestones, as they have no x2.
  5506. * @private
  5507. */
  5508. translatePoint: function (point) {
  5509. var series = this, shapeArgs, size;
  5510. parent.prototype.translatePoint.call(series, point);
  5511. if (point.options.milestone) {
  5512. shapeArgs = point.shapeArgs;
  5513. size = shapeArgs.height;
  5514. point.shapeArgs = {
  5515. x: shapeArgs.x - (size / 2),
  5516. y: shapeArgs.y,
  5517. width: size,
  5518. height: size
  5519. };
  5520. }
  5521. },
  5522. /**
  5523. * Draws a single point in the series.
  5524. *
  5525. * This override draws the point as a diamond if point.options.milestone
  5526. * is true, and uses the original drawPoint() if it is false or not set.
  5527. *
  5528. * @requires highcharts-gantt
  5529. *
  5530. * @private
  5531. * @function Highcharts.seriesTypes.gantt#drawPoint
  5532. *
  5533. * @param {Highcharts.Point} point
  5534. * An instance of Point in the series
  5535. *
  5536. * @param {"animate"|"attr"} verb
  5537. * 'animate' (animates changes) or 'attr' (sets options)
  5538. *
  5539. * @return {void}
  5540. */
  5541. drawPoint: function (point, verb) {
  5542. var series = this, seriesOpts = series.options, renderer = series.chart.renderer, shapeArgs = point.shapeArgs, plotY = point.plotY, graphic = point.graphic, state = point.selected && 'select', cutOff = seriesOpts.stacking && !seriesOpts.borderRadius, diamondShape;
  5543. if (point.options.milestone) {
  5544. if (isNumber(plotY) && point.y !== null && point.visible !== false) {
  5545. diamondShape = renderer.symbols.diamond(shapeArgs.x, shapeArgs.y, shapeArgs.width, shapeArgs.height);
  5546. if (graphic) {
  5547. graphic[verb]({
  5548. d: diamondShape
  5549. });
  5550. }
  5551. else {
  5552. point.graphic = graphic = renderer.path(diamondShape)
  5553. .addClass(point.getClassName(), true)
  5554. .add(point.group || series.group);
  5555. }
  5556. // Presentational
  5557. if (!series.chart.styledMode) {
  5558. point.graphic
  5559. .attr(series.pointAttribs(point, state))
  5560. .shadow(seriesOpts.shadow, null, cutOff);
  5561. }
  5562. }
  5563. else if (graphic) {
  5564. point.graphic = graphic.destroy(); // #1269
  5565. }
  5566. }
  5567. else {
  5568. parent.prototype.drawPoint.call(series, point, verb);
  5569. }
  5570. },
  5571. setData: Series.prototype.setData,
  5572. /**
  5573. * @private
  5574. */
  5575. setGanttPointAliases: function (options) {
  5576. /**
  5577. * Add a value to options if the value exists.
  5578. * @private
  5579. */
  5580. function addIfExists(prop, val) {
  5581. if (typeof val !== 'undefined') {
  5582. options[prop] = val;
  5583. }
  5584. }
  5585. addIfExists('x', pick(options.start, options.x));
  5586. addIfExists('x2', pick(options.end, options.x2));
  5587. addIfExists('partialFill', pick(options.completed, options.partialFill));
  5588. addIfExists('connect', pick(options.dependency, options.connect));
  5589. }
  5590. /* eslint-enable valid-jsdoc */
  5591. }, merge(parent.prototype.pointClass.prototype, {
  5592. // pointProps - point member overrides. We inherit from parent as well.
  5593. /* eslint-disable valid-jsdoc */
  5594. /**
  5595. * Applies the options containing the x and y data and possible some
  5596. * extra properties. This is called on point init or from point.update.
  5597. *
  5598. * @private
  5599. * @function Highcharts.Point#applyOptions
  5600. *
  5601. * @param {object} options
  5602. * The point options
  5603. *
  5604. * @param {number} x
  5605. * The x value
  5606. *
  5607. * @return {Highcharts.Point}
  5608. * The Point instance
  5609. */
  5610. applyOptions: function (options, x) {
  5611. var point = this, retVal = merge(options);
  5612. H.seriesTypes.gantt.prototype.setGanttPointAliases(retVal);
  5613. retVal = parent.prototype.pointClass.prototype.applyOptions
  5614. .call(point, retVal, x);
  5615. return retVal;
  5616. },
  5617. isValid: function () {
  5618. return ((typeof this.start === 'number' ||
  5619. typeof this.x === 'number') &&
  5620. (typeof this.end === 'number' ||
  5621. typeof this.x2 === 'number' ||
  5622. this.milestone));
  5623. }
  5624. /* eslint-enable valid-jsdoc */
  5625. }));
  5626. /**
  5627. * A `gantt` series.
  5628. *
  5629. * @extends series,plotOptions.gantt
  5630. * @excluding boostThreshold, connectors, dashStyle, findNearestPointBy,
  5631. * getExtremesFromAll, marker, negativeColor, pointInterval,
  5632. * pointIntervalUnit, pointPlacement, pointStart
  5633. * @product gantt
  5634. * @requires highcharts-gantt
  5635. * @apioption series.gantt
  5636. */
  5637. /**
  5638. * Data for a Gantt series.
  5639. *
  5640. * @declare Highcharts.GanttPointOptionsObject
  5641. * @type {Array<*>}
  5642. * @extends series.xrange.data
  5643. * @excluding className, color, colorIndex, connect, dataLabels, events,
  5644. * partialFill, selected, x, x2
  5645. * @product gantt
  5646. * @apioption series.gantt.data
  5647. */
  5648. /**
  5649. * Whether the grid node belonging to this point should start as collapsed. Used
  5650. * in axes of type treegrid.
  5651. *
  5652. * @sample {gantt} gantt/treegrid-axis/collapsed/
  5653. * Start as collapsed
  5654. *
  5655. * @type {boolean}
  5656. * @default false
  5657. * @product gantt
  5658. * @apioption series.gantt.data.collapsed
  5659. */
  5660. /**
  5661. * The start time of a task.
  5662. *
  5663. * @type {number}
  5664. * @product gantt
  5665. * @apioption series.gantt.data.start
  5666. */
  5667. /**
  5668. * The end time of a task.
  5669. *
  5670. * @type {number}
  5671. * @product gantt
  5672. * @apioption series.gantt.data.end
  5673. */
  5674. /**
  5675. * The Y value of a task.
  5676. *
  5677. * @type {number}
  5678. * @product gantt
  5679. * @apioption series.gantt.data.y
  5680. */
  5681. /**
  5682. * The name of a task. If a `treegrid` y-axis is used (default in Gantt charts),
  5683. * this will be picked up automatically, and used to calculate the y-value.
  5684. *
  5685. * @type {string}
  5686. * @product gantt
  5687. * @apioption series.gantt.data.name
  5688. */
  5689. /**
  5690. * Progress indicator, how much of the task completed. If it is a number, the
  5691. * `fill` will be applied automatically.
  5692. *
  5693. * @sample {gantt} gantt/demo/progress-indicator
  5694. * Progress indicator
  5695. *
  5696. * @type {number|*}
  5697. * @extends series.xrange.data.partialFill
  5698. * @product gantt
  5699. * @apioption series.gantt.data.completed
  5700. */
  5701. /**
  5702. * The amount of the progress indicator, ranging from 0 (not started) to 1
  5703. * (finished).
  5704. *
  5705. * @type {number}
  5706. * @default 0
  5707. * @apioption series.gantt.data.completed.amount
  5708. */
  5709. /**
  5710. * The fill of the progress indicator. Defaults to a darkened variety of the
  5711. * main color.
  5712. *
  5713. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  5714. * @apioption series.gantt.data.completed.fill
  5715. */
  5716. /**
  5717. * The ID of the point (task) that this point depends on in Gantt charts.
  5718. * Aliases [connect](series.xrange.data.connect). Can also be an object,
  5719. * specifying further connecting [options](series.gantt.connectors) between the
  5720. * points. Multiple connections can be specified by providing an array.
  5721. *
  5722. * @sample gantt/demo/project-management
  5723. * Dependencies
  5724. * @sample gantt/pathfinder/demo
  5725. * Different connection types
  5726. *
  5727. * @type {string|Array<string|*>|*}
  5728. * @extends series.xrange.data.connect
  5729. * @since 6.2.0
  5730. * @product gantt
  5731. * @apioption series.gantt.data.dependency
  5732. */
  5733. /**
  5734. * Whether this point is a milestone. If so, only the `start` option is handled,
  5735. * while `end` is ignored.
  5736. *
  5737. * @sample gantt/gantt/milestones
  5738. * Milestones
  5739. *
  5740. * @type {boolean}
  5741. * @since 6.2.0
  5742. * @product gantt
  5743. * @apioption series.gantt.data.milestone
  5744. */
  5745. /**
  5746. * The ID of the parent point (task) of this point in Gantt charts.
  5747. *
  5748. * @sample gantt/demo/subtasks
  5749. * Gantt chart with subtasks
  5750. *
  5751. * @type {string}
  5752. * @since 6.2.0
  5753. * @product gantt
  5754. * @apioption series.gantt.data.parent
  5755. */
  5756. /**
  5757. * @excluding afterAnimate
  5758. * @apioption series.gantt.events
  5759. */
  5760. ''; // adds doclets above to the transpiled file
  5761. });
  5762. _registerModule(_modules, 'parts-gantt/GanttChart.js', [_modules['parts/Chart.js'], _modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (Chart, H, U) {
  5763. /* *
  5764. *
  5765. * (c) 2016-2020 Highsoft AS
  5766. *
  5767. * Author: Lars A. V. Cabrera
  5768. *
  5769. * License: www.highcharts.com/license
  5770. *
  5771. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  5772. *
  5773. * */
  5774. var getOptions = U.getOptions, isArray = U.isArray, merge = U.merge, splat = U.splat;
  5775. /**
  5776. * Factory function for Gantt charts.
  5777. *
  5778. * @example
  5779. * // Render a chart in to div#container
  5780. * var chart = Highcharts.ganttChart('container', {
  5781. * title: {
  5782. * text: 'My chart'
  5783. * },
  5784. * series: [{
  5785. * data: ...
  5786. * }]
  5787. * });
  5788. *
  5789. * @function Highcharts.ganttChart
  5790. *
  5791. * @param {string|Highcharts.HTMLDOMElement} renderTo
  5792. * The DOM element to render to, or its id.
  5793. *
  5794. * @param {Highcharts.Options} options
  5795. * The chart options structure.
  5796. *
  5797. * @param {Highcharts.ChartCallbackFunction} [callback]
  5798. * Function to run when the chart has loaded and and all external images
  5799. * are loaded. Defining a
  5800. * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load)
  5801. * handler is equivalent.
  5802. *
  5803. * @return {Highcharts.Chart}
  5804. * Returns the Chart object.
  5805. */
  5806. H.ganttChart = function (renderTo, options, callback) {
  5807. var hasRenderToArg = typeof renderTo === 'string' || renderTo.nodeName, seriesOptions = options.series, defaultOptions = getOptions(), defaultLinkedTo, userOptions = options;
  5808. options = arguments[hasRenderToArg ? 1 : 0];
  5809. // If user hasn't defined axes as array, make it into an array and add a
  5810. // second axis by default.
  5811. if (!isArray(options.xAxis)) {
  5812. options.xAxis = [options.xAxis || {}, {}];
  5813. }
  5814. // apply X axis options to both single and multi x axes
  5815. options.xAxis = options.xAxis.map(function (xAxisOptions, i) {
  5816. if (i === 1) { // Second xAxis
  5817. defaultLinkedTo = 0;
  5818. }
  5819. return merge(defaultOptions.xAxis, {
  5820. grid: {
  5821. enabled: true
  5822. },
  5823. opposite: true,
  5824. linkedTo: defaultLinkedTo
  5825. }, xAxisOptions, // user options
  5826. {
  5827. type: 'datetime'
  5828. });
  5829. });
  5830. // apply Y axis options to both single and multi y axes
  5831. options.yAxis = (splat(options.yAxis || {})).map(function (yAxisOptions) {
  5832. return merge(defaultOptions.yAxis, // #3802
  5833. {
  5834. grid: {
  5835. enabled: true
  5836. },
  5837. staticScale: 50,
  5838. reversed: true,
  5839. // Set default type treegrid, but only if 'categories' is
  5840. // undefined
  5841. type: yAxisOptions.categories ? yAxisOptions.type : 'treegrid'
  5842. }, yAxisOptions // user options
  5843. );
  5844. });
  5845. options.series = null;
  5846. options = merge(true, {
  5847. chart: {
  5848. type: 'gantt'
  5849. },
  5850. title: {
  5851. text: null
  5852. },
  5853. legend: {
  5854. enabled: false
  5855. }
  5856. }, options, // user's options
  5857. // forced options
  5858. {
  5859. isGantt: true
  5860. });
  5861. options.series = userOptions.series = seriesOptions;
  5862. (options.series || []).forEach(function (series) {
  5863. if (series.data) {
  5864. series.data.forEach(function (point) {
  5865. H.seriesTypes.gantt.prototype.setGanttPointAliases(point);
  5866. });
  5867. }
  5868. });
  5869. return hasRenderToArg ?
  5870. new Chart(renderTo, options, callback) :
  5871. new Chart(options, options); // @todo does not look correct
  5872. };
  5873. });
  5874. _registerModule(_modules, 'parts/ScrollbarAxis.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
  5875. /* *
  5876. *
  5877. * (c) 2010-2020 Torstein Honsi
  5878. *
  5879. * License: www.highcharts.com/license
  5880. *
  5881. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  5882. *
  5883. * */
  5884. var addEvent = U.addEvent, defined = U.defined, pick = U.pick;
  5885. /* eslint-disable no-invalid-this, valid-jsdoc */
  5886. /**
  5887. * Creates scrollbars if enabled.
  5888. *
  5889. * @private
  5890. */
  5891. var ScrollbarAxis = /** @class */ (function () {
  5892. function ScrollbarAxis() {
  5893. }
  5894. /**
  5895. * Attaches to axis events to create scrollbars if enabled.
  5896. *
  5897. * @private
  5898. *
  5899. * @param AxisClass
  5900. * Axis class to extend.
  5901. *
  5902. * @param ScrollbarClass
  5903. * Scrollbar class to use.
  5904. */
  5905. ScrollbarAxis.compose = function (AxisClass, ScrollbarClass) {
  5906. // Wrap axis initialization and create scrollbar if enabled:
  5907. addEvent(AxisClass, 'afterInit', function () {
  5908. var axis = this;
  5909. if (axis.options &&
  5910. axis.options.scrollbar &&
  5911. axis.options.scrollbar.enabled) {
  5912. // Predefined options:
  5913. axis.options.scrollbar.vertical = !axis.horiz;
  5914. axis.options.startOnTick = axis.options.endOnTick = false;
  5915. axis.scrollbar = new ScrollbarClass(axis.chart.renderer, axis.options.scrollbar, axis.chart);
  5916. addEvent(axis.scrollbar, 'changed', function (e) {
  5917. var axisMin = pick(axis.options && axis.options.min, axis.min), axisMax = pick(axis.options && axis.options.max, axis.max), unitedMin = defined(axis.dataMin) ?
  5918. Math.min(axisMin, axis.min, axis.dataMin) : axisMin, unitedMax = defined(axis.dataMax) ?
  5919. Math.max(axisMax, axis.max, axis.dataMax) : axisMax, range = unitedMax - unitedMin, to, from;
  5920. // #12834, scroll when show/hide series, wrong extremes
  5921. if (!defined(axisMin) || !defined(axisMax)) {
  5922. return;
  5923. }
  5924. if ((axis.horiz && !axis.reversed) ||
  5925. (!axis.horiz && axis.reversed)) {
  5926. to = unitedMin + range * this.to;
  5927. from = unitedMin + range * this.from;
  5928. }
  5929. else {
  5930. // y-values in browser are reversed, but this also
  5931. // applies for reversed horizontal axis:
  5932. to = unitedMin + range * (1 - this.from);
  5933. from = unitedMin + range * (1 - this.to);
  5934. }
  5935. if (pick(this.options.liveRedraw, H.svg && !H.isTouchDevice && !this.chart.isBoosting) ||
  5936. // Mouseup always should change extremes
  5937. e.DOMType === 'mouseup' ||
  5938. // Internal events
  5939. !defined(e.DOMType)) {
  5940. axis.setExtremes(from, to, true, e.DOMType !== 'mousemove', e);
  5941. }
  5942. else {
  5943. // When live redraw is disabled, don't change extremes
  5944. // Only change the position of the scollbar thumb
  5945. this.setRange(this.from, this.to);
  5946. }
  5947. });
  5948. }
  5949. });
  5950. // Wrap rendering axis, and update scrollbar if one is created:
  5951. addEvent(AxisClass, 'afterRender', function () {
  5952. var axis = this, scrollMin = Math.min(pick(axis.options.min, axis.min), axis.min, pick(axis.dataMin, axis.min) // #6930
  5953. ), scrollMax = Math.max(pick(axis.options.max, axis.max), axis.max, pick(axis.dataMax, axis.max) // #6930
  5954. ), scrollbar = axis.scrollbar, offset = axis.axisTitleMargin + (axis.titleOffset || 0), scrollbarsOffsets = axis.chart.scrollbarsOffsets, axisMargin = axis.options.margin || 0, offsetsIndex, from, to;
  5955. if (scrollbar) {
  5956. if (axis.horiz) {
  5957. // Reserve space for labels/title
  5958. if (!axis.opposite) {
  5959. scrollbarsOffsets[1] += offset;
  5960. }
  5961. scrollbar.position(axis.left, axis.top + axis.height + 2 + scrollbarsOffsets[1] -
  5962. (axis.opposite ? axisMargin : 0), axis.width, axis.height);
  5963. // Next scrollbar should reserve space for margin (if set)
  5964. if (!axis.opposite) {
  5965. scrollbarsOffsets[1] += axisMargin;
  5966. }
  5967. offsetsIndex = 1;
  5968. }
  5969. else {
  5970. // Reserve space for labels/title
  5971. if (axis.opposite) {
  5972. scrollbarsOffsets[0] += offset;
  5973. }
  5974. scrollbar.position(axis.left + axis.width + 2 + scrollbarsOffsets[0] -
  5975. (axis.opposite ? 0 : axisMargin), axis.top, axis.width, axis.height);
  5976. // Next scrollbar should reserve space for margin (if set)
  5977. if (axis.opposite) {
  5978. scrollbarsOffsets[0] += axisMargin;
  5979. }
  5980. offsetsIndex = 0;
  5981. }
  5982. scrollbarsOffsets[offsetsIndex] += scrollbar.size +
  5983. scrollbar.options.margin;
  5984. if (isNaN(scrollMin) ||
  5985. isNaN(scrollMax) ||
  5986. !defined(axis.min) ||
  5987. !defined(axis.max) ||
  5988. axis.min === axis.max // #10733
  5989. ) {
  5990. // default action: when extremes are the same or there is
  5991. // not extremes on the axis, but scrollbar exists, make it
  5992. // full size
  5993. scrollbar.setRange(0, 1);
  5994. }
  5995. else {
  5996. from =
  5997. (axis.min - scrollMin) / (scrollMax - scrollMin);
  5998. to =
  5999. (axis.max - scrollMin) / (scrollMax - scrollMin);
  6000. if ((axis.horiz && !axis.reversed) ||
  6001. (!axis.horiz && axis.reversed)) {
  6002. scrollbar.setRange(from, to);
  6003. }
  6004. else {
  6005. // inverse vertical axis
  6006. scrollbar.setRange(1 - to, 1 - from);
  6007. }
  6008. }
  6009. }
  6010. });
  6011. // Make space for a scrollbar:
  6012. addEvent(AxisClass, 'afterGetOffset', function () {
  6013. var axis = this, index = axis.horiz ? 2 : 1, scrollbar = axis.scrollbar;
  6014. if (scrollbar) {
  6015. axis.chart.scrollbarsOffsets = [0, 0]; // reset scrollbars offsets
  6016. axis.chart.axisOffset[index] +=
  6017. scrollbar.size + scrollbar.options.margin;
  6018. }
  6019. });
  6020. };
  6021. return ScrollbarAxis;
  6022. }());
  6023. return ScrollbarAxis;
  6024. });
  6025. _registerModule(_modules, 'parts/Scrollbar.js', [_modules['parts/Axis.js'], _modules['parts/Globals.js'], _modules['parts/ScrollbarAxis.js'], _modules['parts/Utilities.js'], _modules['parts/Options.js']], function (Axis, H, ScrollbarAxis, U, O) {
  6026. /* *
  6027. *
  6028. * (c) 2010-2020 Torstein Honsi
  6029. *
  6030. * License: www.highcharts.com/license
  6031. *
  6032. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  6033. *
  6034. * */
  6035. var addEvent = U.addEvent, correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, fireEvent = U.fireEvent, merge = U.merge, pick = U.pick, removeEvent = U.removeEvent;
  6036. var defaultOptions = O.defaultOptions;
  6037. var hasTouch = H.hasTouch, isTouchDevice = H.isTouchDevice;
  6038. /**
  6039. * When we have vertical scrollbar, rifles and arrow in buttons should be
  6040. * rotated. The same method is used in Navigator's handles, to rotate them.
  6041. *
  6042. * @function Highcharts.swapXY
  6043. *
  6044. * @param {Highcharts.SVGPathArray} path
  6045. * Path to be rotated.
  6046. *
  6047. * @param {boolean} [vertical]
  6048. * If vertical scrollbar, swap x-y values.
  6049. *
  6050. * @return {Highcharts.SVGPathArray}
  6051. * Rotated path.
  6052. *
  6053. * @requires modules/stock
  6054. */
  6055. var swapXY = H.swapXY = function (path, vertical) {
  6056. if (vertical) {
  6057. path.forEach(function (seg) {
  6058. var len = seg.length;
  6059. var temp;
  6060. for (var i = 0; i < len; i += 2) {
  6061. temp = seg[i + 1];
  6062. if (typeof temp === 'number') {
  6063. seg[i + 1] = seg[i + 2];
  6064. seg[i + 2] = temp;
  6065. }
  6066. }
  6067. });
  6068. }
  6069. return path;
  6070. };
  6071. /* eslint-disable no-invalid-this, valid-jsdoc */
  6072. /**
  6073. * A reusable scrollbar, internally used in Highstock's navigator and optionally
  6074. * on individual axes.
  6075. *
  6076. * @private
  6077. * @class
  6078. * @name Highcharts.Scrollbar
  6079. * @param {Highcharts.SVGRenderer} renderer
  6080. * @param {Highcharts.ScrollbarOptions} options
  6081. * @param {Highcharts.Chart} chart
  6082. */
  6083. var Scrollbar = /** @class */ (function () {
  6084. /* *
  6085. *
  6086. * Constructors
  6087. *
  6088. * */
  6089. function Scrollbar(renderer, options, chart) {
  6090. /* *
  6091. *
  6092. * Properties
  6093. *
  6094. * */
  6095. this._events = [];
  6096. this.chartX = 0;
  6097. this.chartY = 0;
  6098. this.from = 0;
  6099. this.group = void 0;
  6100. this.scrollbar = void 0;
  6101. this.scrollbarButtons = [];
  6102. this.scrollbarGroup = void 0;
  6103. this.scrollbarLeft = 0;
  6104. this.scrollbarRifles = void 0;
  6105. this.scrollbarStrokeWidth = 1;
  6106. this.scrollbarTop = 0;
  6107. this.size = 0;
  6108. this.to = 0;
  6109. this.track = void 0;
  6110. this.trackBorderWidth = 1;
  6111. this.userOptions = {};
  6112. this.x = 0;
  6113. this.y = 0;
  6114. this.chart = chart;
  6115. this.options = options;
  6116. this.renderer = chart.renderer;
  6117. this.init(renderer, options, chart);
  6118. }
  6119. /* *
  6120. *
  6121. * Functions
  6122. *
  6123. * */
  6124. /**
  6125. * Set up the mouse and touch events for the Scrollbar
  6126. *
  6127. * @private
  6128. * @function Highcharts.Scrollbar#addEvents
  6129. * @return {void}
  6130. */
  6131. Scrollbar.prototype.addEvents = function () {
  6132. var buttonsOrder = this.options.inverted ? [1, 0] : [0, 1], buttons = this.scrollbarButtons, bar = this.scrollbarGroup.element, track = this.track.element, mouseDownHandler = this.mouseDownHandler.bind(this), mouseMoveHandler = this.mouseMoveHandler.bind(this), mouseUpHandler = this.mouseUpHandler.bind(this), _events;
  6133. // Mouse events
  6134. _events = [
  6135. [buttons[buttonsOrder[0]].element, 'click', this.buttonToMinClick.bind(this)],
  6136. [buttons[buttonsOrder[1]].element, 'click', this.buttonToMaxClick.bind(this)],
  6137. [track, 'click', this.trackClick.bind(this)],
  6138. [bar, 'mousedown', mouseDownHandler],
  6139. [bar.ownerDocument, 'mousemove', mouseMoveHandler],
  6140. [bar.ownerDocument, 'mouseup', mouseUpHandler]
  6141. ];
  6142. // Touch events
  6143. if (hasTouch) {
  6144. _events.push([bar, 'touchstart', mouseDownHandler], [bar.ownerDocument, 'touchmove', mouseMoveHandler], [bar.ownerDocument, 'touchend', mouseUpHandler]);
  6145. }
  6146. // Add them all
  6147. _events.forEach(function (args) {
  6148. addEvent.apply(null, args);
  6149. });
  6150. this._events = _events;
  6151. };
  6152. Scrollbar.prototype.buttonToMaxClick = function (e) {
  6153. var scroller = this;
  6154. var range = (scroller.to - scroller.from) * pick(scroller.options.step, 0.2);
  6155. scroller.updatePosition(scroller.from + range, scroller.to + range);
  6156. fireEvent(scroller, 'changed', {
  6157. from: scroller.from,
  6158. to: scroller.to,
  6159. trigger: 'scrollbar',
  6160. DOMEvent: e
  6161. });
  6162. };
  6163. Scrollbar.prototype.buttonToMinClick = function (e) {
  6164. var scroller = this;
  6165. var range = correctFloat(scroller.to - scroller.from) *
  6166. pick(scroller.options.step, 0.2);
  6167. scroller.updatePosition(correctFloat(scroller.from - range), correctFloat(scroller.to - range));
  6168. fireEvent(scroller, 'changed', {
  6169. from: scroller.from,
  6170. to: scroller.to,
  6171. trigger: 'scrollbar',
  6172. DOMEvent: e
  6173. });
  6174. };
  6175. /**
  6176. * Get normalized (0-1) cursor position over the scrollbar
  6177. *
  6178. * @private
  6179. * @function Highcharts.Scrollbar#cursorToScrollbarPosition
  6180. *
  6181. * @param {*} normalizedEvent
  6182. * normalized event, with chartX and chartY values
  6183. *
  6184. * @return {Highcharts.Dictionary<number>}
  6185. * Local position {chartX, chartY}
  6186. */
  6187. Scrollbar.prototype.cursorToScrollbarPosition = function (normalizedEvent) {
  6188. var scroller = this, options = scroller.options, minWidthDifference = options.minWidth > scroller.calculatedWidth ?
  6189. options.minWidth :
  6190. 0; // minWidth distorts translation
  6191. return {
  6192. chartX: (normalizedEvent.chartX - scroller.x -
  6193. scroller.xOffset) /
  6194. (scroller.barWidth - minWidthDifference),
  6195. chartY: (normalizedEvent.chartY - scroller.y -
  6196. scroller.yOffset) /
  6197. (scroller.barWidth - minWidthDifference)
  6198. };
  6199. };
  6200. /**
  6201. * Destroys allocated elements.
  6202. *
  6203. * @private
  6204. * @function Highcharts.Scrollbar#destroy
  6205. * @return {void}
  6206. */
  6207. Scrollbar.prototype.destroy = function () {
  6208. var scroller = this.chart.scroller;
  6209. // Disconnect events added in addEvents
  6210. this.removeEvents();
  6211. // Destroy properties
  6212. [
  6213. 'track',
  6214. 'scrollbarRifles',
  6215. 'scrollbar',
  6216. 'scrollbarGroup',
  6217. 'group'
  6218. ].forEach(function (prop) {
  6219. if (this[prop] && this[prop].destroy) {
  6220. this[prop] = this[prop].destroy();
  6221. }
  6222. }, this);
  6223. // #6421, chart may have more scrollbars
  6224. if (scroller && this === scroller.scrollbar) {
  6225. scroller.scrollbar = null;
  6226. // Destroy elements in collection
  6227. destroyObjectProperties(scroller.scrollbarButtons);
  6228. }
  6229. };
  6230. /**
  6231. * Draw the scrollbar buttons with arrows
  6232. *
  6233. * @private
  6234. * @function Highcharts.Scrollbar#drawScrollbarButton
  6235. * @param {number} index
  6236. * 0 is left, 1 is right
  6237. * @return {void}
  6238. */
  6239. Scrollbar.prototype.drawScrollbarButton = function (index) {
  6240. var scroller = this, renderer = scroller.renderer, scrollbarButtons = scroller.scrollbarButtons, options = scroller.options, size = scroller.size, group, tempElem;
  6241. group = renderer.g().add(scroller.group);
  6242. scrollbarButtons.push(group);
  6243. // Create a rectangle for the scrollbar button
  6244. tempElem = renderer.rect()
  6245. .addClass('highcharts-scrollbar-button')
  6246. .add(group);
  6247. // Presentational attributes
  6248. if (!this.chart.styledMode) {
  6249. tempElem.attr({
  6250. stroke: options.buttonBorderColor,
  6251. 'stroke-width': options.buttonBorderWidth,
  6252. fill: options.buttonBackgroundColor
  6253. });
  6254. }
  6255. // Place the rectangle based on the rendered stroke width
  6256. tempElem.attr(tempElem.crisp({
  6257. x: -0.5,
  6258. y: -0.5,
  6259. width: size + 1,
  6260. height: size + 1,
  6261. r: options.buttonBorderRadius
  6262. }, tempElem.strokeWidth()));
  6263. // Button arrow
  6264. tempElem = renderer
  6265. .path(swapXY([[
  6266. 'M',
  6267. size / 2 + (index ? -1 : 1),
  6268. size / 2 - 3
  6269. ], [
  6270. 'L',
  6271. size / 2 + (index ? -1 : 1),
  6272. size / 2 + 3
  6273. ], [
  6274. 'L',
  6275. size / 2 + (index ? 2 : -2),
  6276. size / 2
  6277. ]], options.vertical))
  6278. .addClass('highcharts-scrollbar-arrow')
  6279. .add(scrollbarButtons[index]);
  6280. if (!this.chart.styledMode) {
  6281. tempElem.attr({
  6282. fill: options.buttonArrowColor
  6283. });
  6284. }
  6285. };
  6286. /**
  6287. * @private
  6288. * @function Highcharts.Scrollbar#init
  6289. * @param {Highcharts.SVGRenderer} renderer
  6290. * @param {Highcharts.ScrollbarOptions} options
  6291. * @param {Highcharts.Chart} chart
  6292. */
  6293. Scrollbar.prototype.init = function (renderer, options, chart) {
  6294. this.scrollbarButtons = [];
  6295. this.renderer = renderer;
  6296. this.userOptions = options;
  6297. this.options = merge(Scrollbar.defaultOptions, options);
  6298. this.chart = chart;
  6299. // backward compatibility
  6300. this.size = pick(this.options.size, this.options.height);
  6301. // Init
  6302. if (options.enabled) {
  6303. this.render();
  6304. this.addEvents();
  6305. }
  6306. };
  6307. Scrollbar.prototype.mouseDownHandler = function (e) {
  6308. var scroller = this;
  6309. var normalizedEvent = scroller.chart.pointer.normalize(e), mousePosition = scroller.cursorToScrollbarPosition(normalizedEvent);
  6310. scroller.chartX = mousePosition.chartX;
  6311. scroller.chartY = mousePosition.chartY;
  6312. scroller.initPositions = [scroller.from, scroller.to];
  6313. scroller.grabbedCenter = true;
  6314. };
  6315. /**
  6316. * Event handler for the mouse move event.
  6317. * @private
  6318. */
  6319. Scrollbar.prototype.mouseMoveHandler = function (e) {
  6320. var scroller = this;
  6321. var normalizedEvent = scroller.chart.pointer.normalize(e), options = scroller.options, direction = options.vertical ? 'chartY' : 'chartX', initPositions = scroller.initPositions || [], scrollPosition, chartPosition, change;
  6322. // In iOS, a mousemove event with e.pageX === 0 is fired when
  6323. // holding the finger down in the center of the scrollbar. This
  6324. // should be ignored.
  6325. if (scroller.grabbedCenter &&
  6326. // #4696, scrollbar failed on Android
  6327. (!e.touches || e.touches[0][direction] !== 0)) {
  6328. chartPosition = scroller.cursorToScrollbarPosition(normalizedEvent)[direction];
  6329. scrollPosition = scroller[direction];
  6330. change = chartPosition - scrollPosition;
  6331. scroller.hasDragged = true;
  6332. scroller.updatePosition(initPositions[0] + change, initPositions[1] + change);
  6333. if (scroller.hasDragged) {
  6334. fireEvent(scroller, 'changed', {
  6335. from: scroller.from,
  6336. to: scroller.to,
  6337. trigger: 'scrollbar',
  6338. DOMType: e.type,
  6339. DOMEvent: e
  6340. });
  6341. }
  6342. }
  6343. };
  6344. /**
  6345. * Event handler for the mouse up event.
  6346. * @private
  6347. */
  6348. Scrollbar.prototype.mouseUpHandler = function (e) {
  6349. var scroller = this;
  6350. if (scroller.hasDragged) {
  6351. fireEvent(scroller, 'changed', {
  6352. from: scroller.from,
  6353. to: scroller.to,
  6354. trigger: 'scrollbar',
  6355. DOMType: e.type,
  6356. DOMEvent: e
  6357. });
  6358. }
  6359. scroller.grabbedCenter =
  6360. scroller.hasDragged =
  6361. scroller.chartX =
  6362. scroller.chartY = null;
  6363. };
  6364. /**
  6365. * Position the scrollbar, method called from a parent with defined
  6366. * dimensions.
  6367. *
  6368. * @private
  6369. * @function Highcharts.Scrollbar#position
  6370. * @param {number} x
  6371. * x-position on the chart
  6372. * @param {number} y
  6373. * y-position on the chart
  6374. * @param {number} width
  6375. * width of the scrollbar
  6376. * @param {number} height
  6377. * height of the scorllbar
  6378. * @return {void}
  6379. */
  6380. Scrollbar.prototype.position = function (x, y, width, height) {
  6381. var scroller = this, options = scroller.options, vertical = options.vertical, xOffset = height, yOffset = 0, method = scroller.rendered ? 'animate' : 'attr';
  6382. scroller.x = x;
  6383. scroller.y = y + this.trackBorderWidth;
  6384. scroller.width = width; // width with buttons
  6385. scroller.height = height;
  6386. scroller.xOffset = xOffset;
  6387. scroller.yOffset = yOffset;
  6388. // If Scrollbar is a vertical type, swap options:
  6389. if (vertical) {
  6390. scroller.width = scroller.yOffset = width = yOffset = scroller.size;
  6391. scroller.xOffset = xOffset = 0;
  6392. scroller.barWidth = height - width * 2; // width without buttons
  6393. scroller.x = x = x + scroller.options.margin;
  6394. }
  6395. else {
  6396. scroller.height = scroller.xOffset = height = xOffset =
  6397. scroller.size;
  6398. scroller.barWidth = width - height * 2; // width without buttons
  6399. scroller.y = scroller.y + scroller.options.margin;
  6400. }
  6401. // Set general position for a group:
  6402. scroller.group[method]({
  6403. translateX: x,
  6404. translateY: scroller.y
  6405. });
  6406. // Resize background/track:
  6407. scroller.track[method]({
  6408. width: width,
  6409. height: height
  6410. });
  6411. // Move right/bottom button ot it's place:
  6412. scroller.scrollbarButtons[1][method]({
  6413. translateX: vertical ? 0 : width - xOffset,
  6414. translateY: vertical ? height - yOffset : 0
  6415. });
  6416. };
  6417. /**
  6418. * Removes the event handlers attached previously with addEvents.
  6419. *
  6420. * @private
  6421. * @function Highcharts.Scrollbar#removeEvents
  6422. * @return {void}
  6423. */
  6424. Scrollbar.prototype.removeEvents = function () {
  6425. this._events.forEach(function (args) {
  6426. removeEvent.apply(null, args);
  6427. });
  6428. this._events.length = 0;
  6429. };
  6430. /**
  6431. * Render scrollbar with all required items.
  6432. *
  6433. * @private
  6434. * @function Highcharts.Scrollbar#render
  6435. */
  6436. Scrollbar.prototype.render = function () {
  6437. var scroller = this, renderer = scroller.renderer, options = scroller.options, size = scroller.size, styledMode = this.chart.styledMode, group;
  6438. // Draw the scrollbar group
  6439. scroller.group = group = renderer.g('scrollbar').attr({
  6440. zIndex: options.zIndex,
  6441. translateY: -99999
  6442. }).add();
  6443. // Draw the scrollbar track:
  6444. scroller.track = renderer.rect()
  6445. .addClass('highcharts-scrollbar-track')
  6446. .attr({
  6447. x: 0,
  6448. r: options.trackBorderRadius || 0,
  6449. height: size,
  6450. width: size
  6451. }).add(group);
  6452. if (!styledMode) {
  6453. scroller.track.attr({
  6454. fill: options.trackBackgroundColor,
  6455. stroke: options.trackBorderColor,
  6456. 'stroke-width': options.trackBorderWidth
  6457. });
  6458. }
  6459. this.trackBorderWidth = scroller.track.strokeWidth();
  6460. scroller.track.attr({
  6461. y: -this.trackBorderWidth % 2 / 2
  6462. });
  6463. // Draw the scrollbar itself
  6464. scroller.scrollbarGroup = renderer.g().add(group);
  6465. scroller.scrollbar = renderer.rect()
  6466. .addClass('highcharts-scrollbar-thumb')
  6467. .attr({
  6468. height: size,
  6469. width: size,
  6470. r: options.barBorderRadius || 0
  6471. }).add(scroller.scrollbarGroup);
  6472. scroller.scrollbarRifles = renderer
  6473. .path(swapXY([
  6474. ['M', -3, size / 4],
  6475. ['L', -3, 2 * size / 3],
  6476. ['M', 0, size / 4],
  6477. ['L', 0, 2 * size / 3],
  6478. ['M', 3, size / 4],
  6479. ['L', 3, 2 * size / 3]
  6480. ], options.vertical))
  6481. .addClass('highcharts-scrollbar-rifles')
  6482. .add(scroller.scrollbarGroup);
  6483. if (!styledMode) {
  6484. scroller.scrollbar.attr({
  6485. fill: options.barBackgroundColor,
  6486. stroke: options.barBorderColor,
  6487. 'stroke-width': options.barBorderWidth
  6488. });
  6489. scroller.scrollbarRifles.attr({
  6490. stroke: options.rifleColor,
  6491. 'stroke-width': 1
  6492. });
  6493. }
  6494. scroller.scrollbarStrokeWidth = scroller.scrollbar.strokeWidth();
  6495. scroller.scrollbarGroup.translate(-scroller.scrollbarStrokeWidth % 2 / 2, -scroller.scrollbarStrokeWidth % 2 / 2);
  6496. // Draw the buttons:
  6497. scroller.drawScrollbarButton(0);
  6498. scroller.drawScrollbarButton(1);
  6499. };
  6500. /**
  6501. * Set scrollbar size, with a given scale.
  6502. *
  6503. * @private
  6504. * @function Highcharts.Scrollbar#setRange
  6505. * @param {number} from
  6506. * scale (0-1) where bar should start
  6507. * @param {number} to
  6508. * scale (0-1) where bar should end
  6509. * @return {void}
  6510. */
  6511. Scrollbar.prototype.setRange = function (from, to) {
  6512. var scroller = this, options = scroller.options, vertical = options.vertical, minWidth = options.minWidth, fullWidth = scroller.barWidth, fromPX, toPX, newPos, newSize, newRiflesPos, method = (this.rendered &&
  6513. !this.hasDragged &&
  6514. !(this.chart.navigator && this.chart.navigator.hasDragged)) ? 'animate' : 'attr';
  6515. if (!defined(fullWidth)) {
  6516. return;
  6517. }
  6518. from = Math.max(from, 0);
  6519. fromPX = Math.ceil(fullWidth * from);
  6520. toPX = fullWidth * Math.min(to, 1);
  6521. scroller.calculatedWidth = newSize = correctFloat(toPX - fromPX);
  6522. // We need to recalculate position, if minWidth is used
  6523. if (newSize < minWidth) {
  6524. fromPX = (fullWidth - minWidth + newSize) * from;
  6525. newSize = minWidth;
  6526. }
  6527. newPos = Math.floor(fromPX + scroller.xOffset + scroller.yOffset);
  6528. newRiflesPos = newSize / 2 - 0.5; // -0.5 -> rifle line width / 2
  6529. // Store current position:
  6530. scroller.from = from;
  6531. scroller.to = to;
  6532. if (!vertical) {
  6533. scroller.scrollbarGroup[method]({
  6534. translateX: newPos
  6535. });
  6536. scroller.scrollbar[method]({
  6537. width: newSize
  6538. });
  6539. scroller.scrollbarRifles[method]({
  6540. translateX: newRiflesPos
  6541. });
  6542. scroller.scrollbarLeft = newPos;
  6543. scroller.scrollbarTop = 0;
  6544. }
  6545. else {
  6546. scroller.scrollbarGroup[method]({
  6547. translateY: newPos
  6548. });
  6549. scroller.scrollbar[method]({
  6550. height: newSize
  6551. });
  6552. scroller.scrollbarRifles[method]({
  6553. translateY: newRiflesPos
  6554. });
  6555. scroller.scrollbarTop = newPos;
  6556. scroller.scrollbarLeft = 0;
  6557. }
  6558. if (newSize <= 12) {
  6559. scroller.scrollbarRifles.hide();
  6560. }
  6561. else {
  6562. scroller.scrollbarRifles.show(true);
  6563. }
  6564. // Show or hide the scrollbar based on the showFull setting
  6565. if (options.showFull === false) {
  6566. if (from <= 0 && to >= 1) {
  6567. scroller.group.hide();
  6568. }
  6569. else {
  6570. scroller.group.show();
  6571. }
  6572. }
  6573. scroller.rendered = true;
  6574. };
  6575. Scrollbar.prototype.trackClick = function (e) {
  6576. var scroller = this;
  6577. var normalizedEvent = scroller.chart.pointer.normalize(e), range = scroller.to - scroller.from, top = scroller.y + scroller.scrollbarTop, left = scroller.x + scroller.scrollbarLeft;
  6578. if ((scroller.options.vertical && normalizedEvent.chartY > top) ||
  6579. (!scroller.options.vertical && normalizedEvent.chartX > left)) {
  6580. // On the top or on the left side of the track:
  6581. scroller.updatePosition(scroller.from + range, scroller.to + range);
  6582. }
  6583. else {
  6584. // On the bottom or the right side of the track:
  6585. scroller.updatePosition(scroller.from - range, scroller.to - range);
  6586. }
  6587. fireEvent(scroller, 'changed', {
  6588. from: scroller.from,
  6589. to: scroller.to,
  6590. trigger: 'scrollbar',
  6591. DOMEvent: e
  6592. });
  6593. };
  6594. /**
  6595. * Update the scrollbar with new options
  6596. *
  6597. * @private
  6598. * @function Highcharts.Scrollbar#update
  6599. * @param {Highcharts.ScrollbarOptions} options
  6600. * @return {void}
  6601. */
  6602. Scrollbar.prototype.update = function (options) {
  6603. this.destroy();
  6604. this.init(this.chart.renderer, merge(true, this.options, options), this.chart);
  6605. };
  6606. /**
  6607. * Update position option in the Scrollbar, with normalized 0-1 scale
  6608. *
  6609. * @private
  6610. * @function Highcharts.Scrollbar#updatePosition
  6611. * @param {number} from
  6612. * @param {number} to
  6613. * @return {void}
  6614. */
  6615. Scrollbar.prototype.updatePosition = function (from, to) {
  6616. if (to > 1) {
  6617. from = correctFloat(1 - correctFloat(to - from));
  6618. to = 1;
  6619. }
  6620. if (from < 0) {
  6621. to = correctFloat(to - from);
  6622. from = 0;
  6623. }
  6624. this.from = from;
  6625. this.to = to;
  6626. };
  6627. /* *
  6628. *
  6629. * Static Properties
  6630. *
  6631. * */
  6632. /**
  6633. *
  6634. * The scrollbar is a means of panning over the X axis of a stock chart.
  6635. * Scrollbars can also be applied to other types of axes.
  6636. *
  6637. * Another approach to scrollable charts is the [chart.scrollablePlotArea](
  6638. * https://api.highcharts.com/highcharts/chart.scrollablePlotArea) option that
  6639. * is especially suitable for simpler cartesian charts on mobile.
  6640. *
  6641. * In styled mode, all the presentational options for the
  6642. * scrollbar are replaced by the classes `.highcharts-scrollbar-thumb`,
  6643. * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`,
  6644. * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`.
  6645. *
  6646. * @sample stock/yaxis/inverted-bar-scrollbar/
  6647. * A scrollbar on a simple bar chart
  6648. *
  6649. * @product highstock gantt
  6650. * @optionparent scrollbar
  6651. *
  6652. * @private
  6653. */
  6654. Scrollbar.defaultOptions = {
  6655. /**
  6656. * The height of the scrollbar. The height also applies to the width
  6657. * of the scroll arrows so that they are always squares. Defaults to
  6658. * 20 for touch devices and 14 for mouse devices.
  6659. *
  6660. * @sample stock/scrollbar/height/
  6661. * A 30px scrollbar
  6662. *
  6663. * @type {number}
  6664. * @default 20/14
  6665. */
  6666. height: isTouchDevice ? 20 : 14,
  6667. /**
  6668. * The border rounding radius of the bar.
  6669. *
  6670. * @sample stock/scrollbar/style/
  6671. * Scrollbar styling
  6672. */
  6673. barBorderRadius: 0,
  6674. /**
  6675. * The corner radius of the scrollbar buttons.
  6676. *
  6677. * @sample stock/scrollbar/style/
  6678. * Scrollbar styling
  6679. */
  6680. buttonBorderRadius: 0,
  6681. /**
  6682. * Enable or disable the scrollbar.
  6683. *
  6684. * @sample stock/scrollbar/enabled/
  6685. * Disable the scrollbar, only use navigator
  6686. *
  6687. * @type {boolean}
  6688. * @default true
  6689. * @apioption scrollbar.enabled
  6690. */
  6691. /**
  6692. * Whether to redraw the main chart as the scrollbar or the navigator
  6693. * zoomed window is moved. Defaults to `true` for modern browsers and
  6694. * `false` for legacy IE browsers as well as mobile devices.
  6695. *
  6696. * @sample stock/scrollbar/liveredraw
  6697. * Setting live redraw to false
  6698. *
  6699. * @type {boolean}
  6700. * @since 1.3
  6701. */
  6702. liveRedraw: void 0,
  6703. /**
  6704. * The margin between the scrollbar and its axis when the scrollbar is
  6705. * applied directly to an axis.
  6706. */
  6707. margin: 10,
  6708. /**
  6709. * The minimum width of the scrollbar.
  6710. *
  6711. * @since 1.2.5
  6712. */
  6713. minWidth: 6,
  6714. /**
  6715. * Whether to show or hide the scrollbar when the scrolled content is
  6716. * zoomed out to it full extent.
  6717. *
  6718. * @type {boolean}
  6719. * @default true
  6720. * @apioption scrollbar.showFull
  6721. */
  6722. step: 0.2,
  6723. /**
  6724. * The z index of the scrollbar group.
  6725. */
  6726. zIndex: 3,
  6727. /**
  6728. * The background color of the scrollbar itself.
  6729. *
  6730. * @sample stock/scrollbar/style/
  6731. * Scrollbar styling
  6732. *
  6733. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6734. */
  6735. barBackgroundColor: '#cccccc',
  6736. /**
  6737. * The width of the bar's border.
  6738. *
  6739. * @sample stock/scrollbar/style/
  6740. * Scrollbar styling
  6741. */
  6742. barBorderWidth: 1,
  6743. /**
  6744. * The color of the scrollbar's border.
  6745. *
  6746. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6747. */
  6748. barBorderColor: '#cccccc',
  6749. /**
  6750. * The color of the small arrow inside the scrollbar buttons.
  6751. *
  6752. * @sample stock/scrollbar/style/
  6753. * Scrollbar styling
  6754. *
  6755. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6756. */
  6757. buttonArrowColor: '#333333',
  6758. /**
  6759. * The color of scrollbar buttons.
  6760. *
  6761. * @sample stock/scrollbar/style/
  6762. * Scrollbar styling
  6763. *
  6764. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6765. */
  6766. buttonBackgroundColor: '#e6e6e6',
  6767. /**
  6768. * The color of the border of the scrollbar buttons.
  6769. *
  6770. * @sample stock/scrollbar/style/
  6771. * Scrollbar styling
  6772. *
  6773. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6774. */
  6775. buttonBorderColor: '#cccccc',
  6776. /**
  6777. * The border width of the scrollbar buttons.
  6778. *
  6779. * @sample stock/scrollbar/style/
  6780. * Scrollbar styling
  6781. */
  6782. buttonBorderWidth: 1,
  6783. /**
  6784. * The color of the small rifles in the middle of the scrollbar.
  6785. *
  6786. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6787. */
  6788. rifleColor: '#333333',
  6789. /**
  6790. * The color of the track background.
  6791. *
  6792. * @sample stock/scrollbar/style/
  6793. * Scrollbar styling
  6794. *
  6795. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6796. */
  6797. trackBackgroundColor: '#f2f2f2',
  6798. /**
  6799. * The color of the border of the scrollbar track.
  6800. *
  6801. * @sample stock/scrollbar/style/
  6802. * Scrollbar styling
  6803. *
  6804. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  6805. */
  6806. trackBorderColor: '#f2f2f2',
  6807. /**
  6808. * The corner radius of the border of the scrollbar track.
  6809. *
  6810. * @sample stock/scrollbar/style/
  6811. * Scrollbar styling
  6812. *
  6813. * @type {number}
  6814. * @default 0
  6815. * @apioption scrollbar.trackBorderRadius
  6816. */
  6817. /**
  6818. * The width of the border of the scrollbar track.
  6819. *
  6820. * @sample stock/scrollbar/style/
  6821. * Scrollbar styling
  6822. */
  6823. trackBorderWidth: 1
  6824. };
  6825. return Scrollbar;
  6826. }());
  6827. if (!H.Scrollbar) {
  6828. defaultOptions.scrollbar = merge(true, Scrollbar.defaultOptions, defaultOptions.scrollbar);
  6829. H.Scrollbar = Scrollbar;
  6830. ScrollbarAxis.compose(Axis, Scrollbar);
  6831. }
  6832. return H.Scrollbar;
  6833. });
  6834. _registerModule(_modules, 'parts/RangeSelector.js', [_modules['parts/Axis.js'], _modules['parts/Chart.js'], _modules['parts/Globals.js'], _modules['parts/Options.js'], _modules['parts/SVGElement.js'], _modules['parts/Utilities.js']], function (Axis, Chart, H, O, SVGElement, U) {
  6835. /* *
  6836. *
  6837. * (c) 2010-2020 Torstein Honsi
  6838. *
  6839. * License: www.highcharts.com/license
  6840. *
  6841. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  6842. *
  6843. * */
  6844. var defaultOptions = O.defaultOptions;
  6845. var addEvent = U.addEvent, createElement = U.createElement, css = U.css, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, discardElement = U.discardElement, extend = U.extend, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach, pick = U.pick, pInt = U.pInt, splat = U.splat;
  6846. /**
  6847. * Define the time span for the button
  6848. *
  6849. * @typedef {"all"|"day"|"hour"|"millisecond"|"minute"|"month"|"second"|"week"|"year"|"ytd"} Highcharts.RangeSelectorButtonTypeValue
  6850. */
  6851. /**
  6852. * Callback function to react on button clicks.
  6853. *
  6854. * @callback Highcharts.RangeSelectorClickCallbackFunction
  6855. *
  6856. * @param {global.Event} e
  6857. * Event arguments.
  6858. *
  6859. * @param {boolean|undefined}
  6860. * Return false to cancel the default button event.
  6861. */
  6862. /**
  6863. * Callback function to parse values entered in the input boxes and return a
  6864. * valid JavaScript time as milliseconds since 1970.
  6865. *
  6866. * @callback Highcharts.RangeSelectorParseCallbackFunction
  6867. *
  6868. * @param {string} value
  6869. * Input value to parse.
  6870. *
  6871. * @return {number}
  6872. * Parsed JavaScript time value.
  6873. */
  6874. /* ************************************************************************** *
  6875. * Start Range Selector code *
  6876. * ************************************************************************** */
  6877. extend(defaultOptions, {
  6878. /**
  6879. * The range selector is a tool for selecting ranges to display within
  6880. * the chart. It provides buttons to select preconfigured ranges in
  6881. * the chart, like 1 day, 1 week, 1 month etc. It also provides input
  6882. * boxes where min and max dates can be manually input.
  6883. *
  6884. * @product highstock gantt
  6885. * @optionparent rangeSelector
  6886. */
  6887. rangeSelector: {
  6888. /**
  6889. * Whether to enable all buttons from the start. By default buttons are
  6890. * only enabled if the corresponding time range exists on the X axis,
  6891. * but enabling all buttons allows for dynamically loading different
  6892. * time ranges.
  6893. *
  6894. * @sample {highstock} stock/rangeselector/allbuttonsenabled-true/
  6895. * All buttons enabled
  6896. *
  6897. * @type {boolean}
  6898. * @default false
  6899. * @since 2.0.3
  6900. * @apioption rangeSelector.allButtonsEnabled
  6901. */
  6902. /**
  6903. * An array of configuration objects for the buttons.
  6904. *
  6905. * Defaults to:
  6906. * ```js
  6907. * buttons: [{
  6908. * type: 'month',
  6909. * count: 1,
  6910. * text: '1m'
  6911. * }, {
  6912. * type: 'month',
  6913. * count: 3,
  6914. * text: '3m'
  6915. * }, {
  6916. * type: 'month',
  6917. * count: 6,
  6918. * text: '6m'
  6919. * }, {
  6920. * type: 'ytd',
  6921. * text: 'YTD'
  6922. * }, {
  6923. * type: 'year',
  6924. * count: 1,
  6925. * text: '1y'
  6926. * }, {
  6927. * type: 'all',
  6928. * text: 'All'
  6929. * }]
  6930. * ```
  6931. *
  6932. * @sample {highstock} stock/rangeselector/datagrouping/
  6933. * Data grouping by buttons
  6934. *
  6935. * @type {Array<*>}
  6936. * @apioption rangeSelector.buttons
  6937. */
  6938. /**
  6939. * How many units of the defined type the button should span. If `type`
  6940. * is "month" and `count` is 3, the button spans three months.
  6941. *
  6942. * @type {number}
  6943. * @default 1
  6944. * @apioption rangeSelector.buttons.count
  6945. */
  6946. /**
  6947. * Fires when clicking on the rangeSelector button. One parameter,
  6948. * event, is passed to the function, containing common event
  6949. * information.
  6950. *
  6951. * ```js
  6952. * click: function(e) {
  6953. * console.log(this);
  6954. * }
  6955. * ```
  6956. *
  6957. * Return false to stop default button's click action.
  6958. *
  6959. * @sample {highstock} stock/rangeselector/button-click/
  6960. * Click event on the button
  6961. *
  6962. * @type {Highcharts.RangeSelectorClickCallbackFunction}
  6963. * @apioption rangeSelector.buttons.events.click
  6964. */
  6965. /**
  6966. * Additional range (in milliseconds) added to the end of the calculated
  6967. * time span.
  6968. *
  6969. * @sample {highstock} stock/rangeselector/min-max-offsets/
  6970. * Button offsets
  6971. *
  6972. * @type {number}
  6973. * @default 0
  6974. * @since 6.0.0
  6975. * @apioption rangeSelector.buttons.offsetMax
  6976. */
  6977. /**
  6978. * Additional range (in milliseconds) added to the start of the
  6979. * calculated time span.
  6980. *
  6981. * @sample {highstock} stock/rangeselector/min-max-offsets/
  6982. * Button offsets
  6983. *
  6984. * @type {number}
  6985. * @default 0
  6986. * @since 6.0.0
  6987. * @apioption rangeSelector.buttons.offsetMin
  6988. */
  6989. /**
  6990. * When buttons apply dataGrouping on a series, by default zooming
  6991. * in/out will deselect buttons and unset dataGrouping. Enable this
  6992. * option to keep buttons selected when extremes change.
  6993. *
  6994. * @sample {highstock} stock/rangeselector/preserve-datagrouping/
  6995. * Different preserveDataGrouping settings
  6996. *
  6997. * @type {boolean}
  6998. * @default false
  6999. * @since 6.1.2
  7000. * @apioption rangeSelector.buttons.preserveDataGrouping
  7001. */
  7002. /**
  7003. * A custom data grouping object for each button.
  7004. *
  7005. * @see [series.dataGrouping](#plotOptions.series.dataGrouping)
  7006. *
  7007. * @sample {highstock} stock/rangeselector/datagrouping/
  7008. * Data grouping by range selector buttons
  7009. *
  7010. * @type {*}
  7011. * @extends plotOptions.series.dataGrouping
  7012. * @apioption rangeSelector.buttons.dataGrouping
  7013. */
  7014. /**
  7015. * The text for the button itself.
  7016. *
  7017. * @type {string}
  7018. * @apioption rangeSelector.buttons.text
  7019. */
  7020. /**
  7021. * Defined the time span for the button. Can be one of `millisecond`,
  7022. * `second`, `minute`, `hour`, `day`, `week`, `month`, `year`, `ytd`,
  7023. * and `all`.
  7024. *
  7025. * @type {Highcharts.RangeSelectorButtonTypeValue}
  7026. * @apioption rangeSelector.buttons.type
  7027. */
  7028. /**
  7029. * The space in pixels between the buttons in the range selector.
  7030. *
  7031. * @type {number}
  7032. * @default 0
  7033. * @apioption rangeSelector.buttonSpacing
  7034. */
  7035. /**
  7036. * Enable or disable the range selector.
  7037. *
  7038. * @sample {highstock} stock/rangeselector/enabled/
  7039. * Disable the range selector
  7040. *
  7041. * @type {boolean}
  7042. * @default true
  7043. * @apioption rangeSelector.enabled
  7044. */
  7045. /**
  7046. * The vertical alignment of the rangeselector box. Allowed properties
  7047. * are `top`, `middle`, `bottom`.
  7048. *
  7049. * @sample {highstock} stock/rangeselector/vertical-align-middle/
  7050. * Middle
  7051. * @sample {highstock} stock/rangeselector/vertical-align-bottom/
  7052. * Bottom
  7053. *
  7054. * @type {Highcharts.VerticalAlignValue}
  7055. * @since 6.0.0
  7056. */
  7057. verticalAlign: 'top',
  7058. /**
  7059. * A collection of attributes for the buttons. The object takes SVG
  7060. * attributes like `fill`, `stroke`, `stroke-width`, as well as `style`,
  7061. * a collection of CSS properties for the text.
  7062. *
  7063. * The object can also be extended with states, so you can set
  7064. * presentational options for `hover`, `select` or `disabled` button
  7065. * states.
  7066. *
  7067. * CSS styles for the text label.
  7068. *
  7069. * In styled mode, the buttons are styled by the
  7070. * `.highcharts-range-selector-buttons .highcharts-button` rule with its
  7071. * different states.
  7072. *
  7073. * @sample {highstock} stock/rangeselector/styling/
  7074. * Styling the buttons and inputs
  7075. *
  7076. * @type {Highcharts.SVGAttributes}
  7077. */
  7078. buttonTheme: {
  7079. /** @ignore */
  7080. width: 28,
  7081. /** @ignore */
  7082. height: 18,
  7083. /** @ignore */
  7084. padding: 2,
  7085. /** @ignore */
  7086. zIndex: 7 // #484, #852
  7087. },
  7088. /**
  7089. * When the rangeselector is floating, the plot area does not reserve
  7090. * space for it. This opens for positioning anywhere on the chart.
  7091. *
  7092. * @sample {highstock} stock/rangeselector/floating/
  7093. * Placing the range selector between the plot area and the
  7094. * navigator
  7095. *
  7096. * @since 6.0.0
  7097. */
  7098. floating: false,
  7099. /**
  7100. * The x offset of the range selector relative to its horizontal
  7101. * alignment within `chart.spacingLeft` and `chart.spacingRight`.
  7102. *
  7103. * @since 6.0.0
  7104. */
  7105. x: 0,
  7106. /**
  7107. * The y offset of the range selector relative to its horizontal
  7108. * alignment within `chart.spacingLeft` and `chart.spacingRight`.
  7109. *
  7110. * @since 6.0.0
  7111. */
  7112. y: 0,
  7113. /**
  7114. * Deprecated. The height of the range selector. Currently it is
  7115. * calculated dynamically.
  7116. *
  7117. * @deprecated
  7118. * @type {number|undefined}
  7119. * @since 2.1.9
  7120. */
  7121. height: void 0,
  7122. /**
  7123. * The border color of the date input boxes.
  7124. *
  7125. * @sample {highstock} stock/rangeselector/styling/
  7126. * Styling the buttons and inputs
  7127. *
  7128. * @type {Highcharts.ColorString}
  7129. * @default #cccccc
  7130. * @since 1.3.7
  7131. * @apioption rangeSelector.inputBoxBorderColor
  7132. */
  7133. /**
  7134. * The pixel height of the date input boxes.
  7135. *
  7136. * @sample {highstock} stock/rangeselector/styling/
  7137. * Styling the buttons and inputs
  7138. *
  7139. * @type {number}
  7140. * @default 17
  7141. * @since 1.3.7
  7142. * @apioption rangeSelector.inputBoxHeight
  7143. */
  7144. /**
  7145. * CSS for the container DIV holding the input boxes. Deprecated as
  7146. * of 1.2.5\. Use [inputPosition](#rangeSelector.inputPosition) instead.
  7147. *
  7148. * @sample {highstock} stock/rangeselector/styling/
  7149. * Styling the buttons and inputs
  7150. *
  7151. * @deprecated
  7152. * @type {Highcharts.CSSObject}
  7153. * @apioption rangeSelector.inputBoxStyle
  7154. */
  7155. /**
  7156. * The pixel width of the date input boxes.
  7157. *
  7158. * @sample {highstock} stock/rangeselector/styling/
  7159. * Styling the buttons and inputs
  7160. *
  7161. * @type {number}
  7162. * @default 90
  7163. * @since 1.3.7
  7164. * @apioption rangeSelector.inputBoxWidth
  7165. */
  7166. /**
  7167. * The date format in the input boxes when not selected for editing.
  7168. * Defaults to `%b %e, %Y`.
  7169. *
  7170. * @sample {highstock} stock/rangeselector/input-format/
  7171. * Milliseconds in the range selector
  7172. *
  7173. * @type {string}
  7174. * @default %b %e, %Y
  7175. * @apioption rangeSelector.inputDateFormat
  7176. */
  7177. /**
  7178. * A custom callback function to parse values entered in the input boxes
  7179. * and return a valid JavaScript time as milliseconds since 1970.
  7180. *
  7181. * @sample {highstock} stock/rangeselector/input-format/
  7182. * Milliseconds in the range selector
  7183. *
  7184. * @type {Highcharts.RangeSelectorParseCallbackFunction}
  7185. * @since 1.3.3
  7186. * @apioption rangeSelector.inputDateParser
  7187. */
  7188. /**
  7189. * The date format in the input boxes when they are selected for
  7190. * editing. This must be a format that is recognized by JavaScript
  7191. * Date.parse.
  7192. *
  7193. * @sample {highstock} stock/rangeselector/input-format/
  7194. * Milliseconds in the range selector
  7195. *
  7196. * @type {string}
  7197. * @default %Y-%m-%d
  7198. * @apioption rangeSelector.inputEditDateFormat
  7199. */
  7200. /**
  7201. * Enable or disable the date input boxes. Defaults to enabled when
  7202. * there is enough space, disabled if not (typically mobile).
  7203. *
  7204. * @sample {highstock} stock/rangeselector/input-datepicker/
  7205. * Extending the input with a jQuery UI datepicker
  7206. *
  7207. * @type {boolean}
  7208. * @default true
  7209. * @apioption rangeSelector.inputEnabled
  7210. */
  7211. /**
  7212. * Positioning for the input boxes. Allowed properties are `align`,
  7213. * `x` and `y`.
  7214. *
  7215. * @since 1.2.4
  7216. */
  7217. inputPosition: {
  7218. /**
  7219. * The alignment of the input box. Allowed properties are `left`,
  7220. * `center`, `right`.
  7221. *
  7222. * @sample {highstock} stock/rangeselector/input-button-position/
  7223. * Alignment
  7224. *
  7225. * @type {Highcharts.AlignValue}
  7226. * @since 6.0.0
  7227. */
  7228. align: 'right',
  7229. /**
  7230. * X offset of the input row.
  7231. */
  7232. x: 0,
  7233. /**
  7234. * Y offset of the input row.
  7235. */
  7236. y: 0
  7237. },
  7238. /**
  7239. * The index of the button to appear pre-selected.
  7240. *
  7241. * @type {number}
  7242. * @apioption rangeSelector.selected
  7243. */
  7244. /**
  7245. * Positioning for the button row.
  7246. *
  7247. * @since 1.2.4
  7248. */
  7249. buttonPosition: {
  7250. /**
  7251. * The alignment of the input box. Allowed properties are `left`,
  7252. * `center`, `right`.
  7253. *
  7254. * @sample {highstock} stock/rangeselector/input-button-position/
  7255. * Alignment
  7256. *
  7257. * @type {Highcharts.AlignValue}
  7258. * @since 6.0.0
  7259. */
  7260. align: 'left',
  7261. /**
  7262. * X offset of the button row.
  7263. */
  7264. x: 0,
  7265. /**
  7266. * Y offset of the button row.
  7267. */
  7268. y: 0
  7269. },
  7270. /**
  7271. * CSS for the HTML inputs in the range selector.
  7272. *
  7273. * In styled mode, the inputs are styled by the
  7274. * `.highcharts-range-input text` rule in SVG mode, and
  7275. * `input.highcharts-range-selector` when active.
  7276. *
  7277. * @sample {highstock} stock/rangeselector/styling/
  7278. * Styling the buttons and inputs
  7279. *
  7280. * @type {Highcharts.CSSObject}
  7281. * @apioption rangeSelector.inputStyle
  7282. */
  7283. /**
  7284. * CSS styles for the labels - the Zoom, From and To texts.
  7285. *
  7286. * In styled mode, the labels are styled by the
  7287. * `.highcharts-range-label` class.
  7288. *
  7289. * @sample {highstock} stock/rangeselector/styling/
  7290. * Styling the buttons and inputs
  7291. *
  7292. * @type {Highcharts.CSSObject}
  7293. */
  7294. labelStyle: {
  7295. /** @ignore */
  7296. color: '#666666'
  7297. }
  7298. }
  7299. });
  7300. defaultOptions.lang = merge(defaultOptions.lang,
  7301. /**
  7302. * Language object. The language object is global and it can't be set
  7303. * on each chart initialization. Instead, use `Highcharts.setOptions` to
  7304. * set it before any chart is initialized.
  7305. *
  7306. * ```js
  7307. * Highcharts.setOptions({
  7308. * lang: {
  7309. * months: [
  7310. * 'Janvier', 'Février', 'Mars', 'Avril',
  7311. * 'Mai', 'Juin', 'Juillet', 'Août',
  7312. * 'Septembre', 'Octobre', 'Novembre', 'Décembre'
  7313. * ],
  7314. * weekdays: [
  7315. * 'Dimanche', 'Lundi', 'Mardi', 'Mercredi',
  7316. * 'Jeudi', 'Vendredi', 'Samedi'
  7317. * ]
  7318. * }
  7319. * });
  7320. * ```
  7321. *
  7322. * @optionparent lang
  7323. */
  7324. {
  7325. /**
  7326. * The text for the label for the range selector buttons.
  7327. *
  7328. * @product highstock gantt
  7329. */
  7330. rangeSelectorZoom: 'Zoom',
  7331. /**
  7332. * The text for the label for the "from" input box in the range
  7333. * selector.
  7334. *
  7335. * @product highstock gantt
  7336. */
  7337. rangeSelectorFrom: 'From',
  7338. /**
  7339. * The text for the label for the "to" input box in the range selector.
  7340. *
  7341. * @product highstock gantt
  7342. */
  7343. rangeSelectorTo: 'To'
  7344. });
  7345. /* eslint-disable no-invalid-this, valid-jsdoc */
  7346. /**
  7347. * The range selector.
  7348. *
  7349. * @private
  7350. * @class
  7351. * @name Highcharts.RangeSelector
  7352. * @param {Highcharts.Chart} chart
  7353. */
  7354. var RangeSelector = /** @class */ (function () {
  7355. function RangeSelector(chart) {
  7356. /* *
  7357. *
  7358. * Properties
  7359. *
  7360. * */
  7361. this.buttons = void 0;
  7362. this.buttonOptions = RangeSelector.prototype.defaultButtons;
  7363. this.options = void 0;
  7364. this.chart = chart;
  7365. // Run RangeSelector
  7366. this.init(chart);
  7367. }
  7368. /**
  7369. * The method to run when one of the buttons in the range selectors is
  7370. * clicked
  7371. *
  7372. * @private
  7373. * @function Highcharts.RangeSelector#clickButton
  7374. * @param {number} i
  7375. * The index of the button
  7376. * @param {boolean} [redraw]
  7377. * @return {void}
  7378. */
  7379. RangeSelector.prototype.clickButton = function (i, redraw) {
  7380. var rangeSelector = this, chart = rangeSelector.chart, rangeOptions = rangeSelector.buttonOptions[i], baseAxis = chart.xAxis[0], unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis || {}, dataMin = unionExtremes.dataMin, dataMax = unionExtremes.dataMax, newMin, newMax = baseAxis && Math.round(Math.min(baseAxis.max, pick(dataMax, baseAxis.max))), // #1568
  7381. type = rangeOptions.type, baseXAxisOptions, range = rangeOptions._range, rangeMin, minSetting, rangeSetting, ctx, ytdExtremes, dataGrouping = rangeOptions.dataGrouping;
  7382. // chart has no data, base series is removed
  7383. if (dataMin === null || dataMax === null) {
  7384. return;
  7385. }
  7386. // Set the fixed range before range is altered
  7387. chart.fixedRange = range;
  7388. // Apply dataGrouping associated to button
  7389. if (dataGrouping) {
  7390. this.forcedDataGrouping = true;
  7391. Axis.prototype.setDataGrouping.call(baseAxis || { chart: this.chart }, dataGrouping, false);
  7392. this.frozenStates = rangeOptions.preserveDataGrouping;
  7393. }
  7394. // Apply range
  7395. if (type === 'month' || type === 'year') {
  7396. if (!baseAxis) {
  7397. // This is set to the user options and picked up later when the
  7398. // axis is instantiated so that we know the min and max.
  7399. range = rangeOptions;
  7400. }
  7401. else {
  7402. ctx = {
  7403. range: rangeOptions,
  7404. max: newMax,
  7405. chart: chart,
  7406. dataMin: dataMin,
  7407. dataMax: dataMax
  7408. };
  7409. newMin = baseAxis.minFromRange.call(ctx);
  7410. if (isNumber(ctx.newMax)) {
  7411. newMax = ctx.newMax;
  7412. }
  7413. }
  7414. // Fixed times like minutes, hours, days
  7415. }
  7416. else if (range) {
  7417. newMin = Math.max(newMax - range, dataMin);
  7418. newMax = Math.min(newMin + range, dataMax);
  7419. }
  7420. else if (type === 'ytd') {
  7421. // On user clicks on the buttons, or a delayed action running from
  7422. // the beforeRender event (below), the baseAxis is defined.
  7423. if (baseAxis) {
  7424. // When "ytd" is the pre-selected button for the initial view,
  7425. // its calculation is delayed and rerun in the beforeRender
  7426. // event (below). When the series are initialized, but before
  7427. // the chart is rendered, we have access to the xData array
  7428. // (#942).
  7429. if (typeof dataMax === 'undefined') {
  7430. dataMin = Number.MAX_VALUE;
  7431. dataMax = Number.MIN_VALUE;
  7432. chart.series.forEach(function (series) {
  7433. // reassign it to the last item
  7434. var xData = series.xData;
  7435. dataMin = Math.min(xData[0], dataMin);
  7436. dataMax = Math.max(xData[xData.length - 1], dataMax);
  7437. });
  7438. redraw = false;
  7439. }
  7440. ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, chart.time.useUTC);
  7441. newMin = rangeMin = ytdExtremes.min;
  7442. newMax = ytdExtremes.max;
  7443. // "ytd" is pre-selected. We don't yet have access to processed
  7444. // point and extremes data (things like pointStart and pointInterval
  7445. // are missing), so we delay the process (#942)
  7446. }
  7447. else {
  7448. rangeSelector.deferredYTDClick = i;
  7449. return;
  7450. }
  7451. }
  7452. else if (type === 'all' && baseAxis) {
  7453. newMin = dataMin;
  7454. newMax = dataMax;
  7455. }
  7456. newMin += rangeOptions._offsetMin;
  7457. newMax += rangeOptions._offsetMax;
  7458. rangeSelector.setSelected(i);
  7459. // Update the chart
  7460. if (!baseAxis) {
  7461. // Axis not yet instanciated. Temporarily set min and range
  7462. // options and remove them on chart load (#4317).
  7463. baseXAxisOptions = splat(chart.options.xAxis)[0];
  7464. rangeSetting = baseXAxisOptions.range;
  7465. baseXAxisOptions.range = range;
  7466. minSetting = baseXAxisOptions.min;
  7467. baseXAxisOptions.min = rangeMin;
  7468. addEvent(chart, 'load', function resetMinAndRange() {
  7469. baseXAxisOptions.range = rangeSetting;
  7470. baseXAxisOptions.min = minSetting;
  7471. });
  7472. }
  7473. else {
  7474. // Existing axis object. Set extremes after render time.
  7475. baseAxis.setExtremes(newMin, newMax, pick(redraw, 1), null, // auto animation
  7476. {
  7477. trigger: 'rangeSelectorButton',
  7478. rangeSelectorButton: rangeOptions
  7479. });
  7480. }
  7481. };
  7482. /**
  7483. * Set the selected option. This method only sets the internal flag, it
  7484. * doesn't update the buttons or the actual zoomed range.
  7485. *
  7486. * @private
  7487. * @function Highcharts.RangeSelector#setSelected
  7488. * @param {number} [selected]
  7489. * @return {void}
  7490. */
  7491. RangeSelector.prototype.setSelected = function (selected) {
  7492. this.selected = this.options.selected = selected;
  7493. };
  7494. /**
  7495. * Initialize the range selector
  7496. *
  7497. * @private
  7498. * @function Highcharts.RangeSelector#init
  7499. * @param {Highcharts.Chart} chart
  7500. * @return {void}
  7501. */
  7502. RangeSelector.prototype.init = function (chart) {
  7503. var rangeSelector = this, options = chart.options.rangeSelector, buttonOptions = options.buttons || rangeSelector.defaultButtons.slice(), selectedOption = options.selected, blurInputs = function () {
  7504. var minInput = rangeSelector.minInput, maxInput = rangeSelector.maxInput;
  7505. // #3274 in some case blur is not defined
  7506. if (minInput && minInput.blur) {
  7507. fireEvent(minInput, 'blur');
  7508. }
  7509. if (maxInput && maxInput.blur) {
  7510. fireEvent(maxInput, 'blur');
  7511. }
  7512. };
  7513. rangeSelector.chart = chart;
  7514. rangeSelector.options = options;
  7515. rangeSelector.buttons = [];
  7516. rangeSelector.buttonOptions = buttonOptions;
  7517. this.unMouseDown = addEvent(chart.container, 'mousedown', blurInputs);
  7518. this.unResize = addEvent(chart, 'resize', blurInputs);
  7519. // Extend the buttonOptions with actual range
  7520. buttonOptions.forEach(rangeSelector.computeButtonRange);
  7521. // zoomed range based on a pre-selected button index
  7522. if (typeof selectedOption !== 'undefined' &&
  7523. buttonOptions[selectedOption]) {
  7524. this.clickButton(selectedOption, false);
  7525. }
  7526. addEvent(chart, 'load', function () {
  7527. // If a data grouping is applied to the current button, release it
  7528. // when extremes change
  7529. if (chart.xAxis && chart.xAxis[0]) {
  7530. addEvent(chart.xAxis[0], 'setExtremes', function (e) {
  7531. if (this.max - this.min !==
  7532. chart.fixedRange &&
  7533. e.trigger !== 'rangeSelectorButton' &&
  7534. e.trigger !== 'updatedData' &&
  7535. rangeSelector.forcedDataGrouping &&
  7536. !rangeSelector.frozenStates) {
  7537. this.setDataGrouping(false, false);
  7538. }
  7539. });
  7540. }
  7541. });
  7542. };
  7543. /**
  7544. * Dynamically update the range selector buttons after a new range has been
  7545. * set
  7546. *
  7547. * @private
  7548. * @function Highcharts.RangeSelector#updateButtonStates
  7549. * @return {void}
  7550. */
  7551. RangeSelector.prototype.updateButtonStates = function () {
  7552. var rangeSelector = this, chart = this.chart, baseAxis = chart.xAxis[0], actualRange = Math.round(baseAxis.max - baseAxis.min), hasNoData = !baseAxis.hasVisibleSeries, day = 24 * 36e5, // A single day in milliseconds
  7553. unionExtremes = (chart.scroller &&
  7554. chart.scroller.getUnionExtremes()) || baseAxis, dataMin = unionExtremes.dataMin, dataMax = unionExtremes.dataMax, ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, chart.time.useUTC), ytdMin = ytdExtremes.min, ytdMax = ytdExtremes.max, selected = rangeSelector.selected, selectedExists = isNumber(selected), allButtonsEnabled = rangeSelector.options.allButtonsEnabled, buttons = rangeSelector.buttons;
  7555. rangeSelector.buttonOptions.forEach(function (rangeOptions, i) {
  7556. var range = rangeOptions._range, type = rangeOptions.type, count = rangeOptions.count || 1, button = buttons[i], state = 0, disable, select, offsetRange = rangeOptions._offsetMax -
  7557. rangeOptions._offsetMin, isSelected = i === selected,
  7558. // Disable buttons where the range exceeds what is allowed in
  7559. // the current view
  7560. isTooGreatRange = range >
  7561. dataMax - dataMin,
  7562. // Disable buttons where the range is smaller than the minimum
  7563. // range
  7564. isTooSmallRange = range < baseAxis.minRange,
  7565. // Do not select the YTD button if not explicitly told so
  7566. isYTDButNotSelected = false,
  7567. // Disable the All button if we're already showing all
  7568. isAllButAlreadyShowingAll = false, isSameRange = range === actualRange;
  7569. // Months and years have a variable range so we check the extremes
  7570. if ((type === 'month' || type === 'year') &&
  7571. (actualRange + 36e5 >=
  7572. { month: 28, year: 365 }[type] * day * count - offsetRange) &&
  7573. (actualRange - 36e5 <=
  7574. { month: 31, year: 366 }[type] * day * count + offsetRange)) {
  7575. isSameRange = true;
  7576. }
  7577. else if (type === 'ytd') {
  7578. isSameRange = (ytdMax - ytdMin + offsetRange) === actualRange;
  7579. isYTDButNotSelected = !isSelected;
  7580. }
  7581. else if (type === 'all') {
  7582. isSameRange = (baseAxis.max - baseAxis.min >=
  7583. dataMax - dataMin);
  7584. isAllButAlreadyShowingAll = (!isSelected &&
  7585. selectedExists &&
  7586. isSameRange);
  7587. }
  7588. // The new zoom area happens to match the range for a button - mark
  7589. // it selected. This happens when scrolling across an ordinal gap.
  7590. // It can be seen in the intraday demos when selecting 1h and scroll
  7591. // across the night gap.
  7592. disable = (!allButtonsEnabled &&
  7593. (isTooGreatRange ||
  7594. isTooSmallRange ||
  7595. isAllButAlreadyShowingAll ||
  7596. hasNoData));
  7597. select = ((isSelected && isSameRange) ||
  7598. (isSameRange && !selectedExists && !isYTDButNotSelected) ||
  7599. (isSelected && rangeSelector.frozenStates));
  7600. if (disable) {
  7601. state = 3;
  7602. }
  7603. else if (select) {
  7604. selectedExists = true; // Only one button can be selected
  7605. state = 2;
  7606. }
  7607. // If state has changed, update the button
  7608. if (button.state !== state) {
  7609. button.setState(state);
  7610. // Reset (#9209)
  7611. if (state === 0 && selected === i) {
  7612. rangeSelector.setSelected(null);
  7613. }
  7614. }
  7615. });
  7616. };
  7617. /**
  7618. * Compute and cache the range for an individual button
  7619. *
  7620. * @private
  7621. * @function Highcharts.RangeSelector#computeButtonRange
  7622. * @param {Highcharts.RangeSelectorButtonsOptions} rangeOptions
  7623. * @return {void}
  7624. */
  7625. RangeSelector.prototype.computeButtonRange = function (rangeOptions) {
  7626. var type = rangeOptions.type, count = rangeOptions.count || 1,
  7627. // these time intervals have a fixed number of milliseconds, as
  7628. // opposed to month, ytd and year
  7629. fixedTimes = {
  7630. millisecond: 1,
  7631. second: 1000,
  7632. minute: 60 * 1000,
  7633. hour: 3600 * 1000,
  7634. day: 24 * 3600 * 1000,
  7635. week: 7 * 24 * 3600 * 1000
  7636. };
  7637. // Store the range on the button object
  7638. if (fixedTimes[type]) {
  7639. rangeOptions._range = fixedTimes[type] * count;
  7640. }
  7641. else if (type === 'month' || type === 'year') {
  7642. rangeOptions._range = {
  7643. month: 30,
  7644. year: 365
  7645. }[type] * 24 * 36e5 * count;
  7646. }
  7647. rangeOptions._offsetMin = pick(rangeOptions.offsetMin, 0);
  7648. rangeOptions._offsetMax = pick(rangeOptions.offsetMax, 0);
  7649. rangeOptions._range +=
  7650. rangeOptions._offsetMax - rangeOptions._offsetMin;
  7651. };
  7652. /**
  7653. * Set the internal and displayed value of a HTML input for the dates
  7654. *
  7655. * @private
  7656. * @function Highcharts.RangeSelector#setInputValue
  7657. * @param {string} name
  7658. * @param {number} [inputTime]
  7659. * @return {void}
  7660. */
  7661. RangeSelector.prototype.setInputValue = function (name, inputTime) {
  7662. var options = this.chart.options.rangeSelector, time = this.chart.time, input = this[name + 'Input'];
  7663. if (defined(inputTime)) {
  7664. input.previousValue = input.HCTime;
  7665. input.HCTime = inputTime;
  7666. }
  7667. input.value = time.dateFormat(options.inputEditDateFormat || '%Y-%m-%d', input.HCTime);
  7668. this[name + 'DateBox'].attr({
  7669. text: time.dateFormat(options.inputDateFormat || '%b %e, %Y', input.HCTime)
  7670. });
  7671. };
  7672. /**
  7673. * @private
  7674. * @function Highcharts.RangeSelector#showInput
  7675. * @param {string} name
  7676. * @return {void}
  7677. */
  7678. RangeSelector.prototype.showInput = function (name) {
  7679. var inputGroup = this.inputGroup, dateBox = this[name + 'DateBox'];
  7680. css(this[name + 'Input'], {
  7681. left: (inputGroup.translateX + dateBox.x) + 'px',
  7682. top: inputGroup.translateY + 'px',
  7683. width: (dateBox.width - 2) + 'px',
  7684. height: (dateBox.height - 2) + 'px',
  7685. border: '2px solid silver'
  7686. });
  7687. };
  7688. /**
  7689. * @private
  7690. * @function Highcharts.RangeSelector#hideInput
  7691. * @param {string} name
  7692. * @return {void}
  7693. */
  7694. RangeSelector.prototype.hideInput = function (name) {
  7695. css(this[name + 'Input'], {
  7696. border: 0,
  7697. width: '1px',
  7698. height: '1px'
  7699. });
  7700. this.setInputValue(name);
  7701. };
  7702. /**
  7703. * Draw either the 'from' or the 'to' HTML input box of the range selector
  7704. *
  7705. * @private
  7706. * @function Highcharts.RangeSelector#drawInput
  7707. * @param {string} name
  7708. * @return {void}
  7709. */
  7710. RangeSelector.prototype.drawInput = function (name) {
  7711. var rangeSelector = this, chart = rangeSelector.chart, chartStyle = chart.renderer.style || {}, renderer = chart.renderer, options = chart.options.rangeSelector, lang = defaultOptions.lang, div = rangeSelector.div, isMin = name === 'min', input, label, dateBox, inputGroup = this.inputGroup;
  7712. /**
  7713. * @private
  7714. */
  7715. function updateExtremes() {
  7716. var inputValue = input.value, value = (options.inputDateParser || Date.parse)(inputValue), chartAxis = chart.xAxis[0], dataAxis = chart.scroller && chart.scroller.xAxis ?
  7717. chart.scroller.xAxis :
  7718. chartAxis, dataMin = dataAxis.dataMin, dataMax = dataAxis.dataMax;
  7719. if (value !== input.previousValue) {
  7720. input.previousValue = value;
  7721. // If the value isn't parsed directly to a value by the
  7722. // browser's Date.parse method, like YYYY-MM-DD in IE, try
  7723. // parsing it a different way
  7724. if (!isNumber(value)) {
  7725. value = inputValue.split('-');
  7726. value = Date.UTC(pInt(value[0]), pInt(value[1]) - 1, pInt(value[2]));
  7727. }
  7728. if (isNumber(value)) {
  7729. // Correct for timezone offset (#433)
  7730. if (!chart.time.useUTC) {
  7731. value =
  7732. value + new Date().getTimezoneOffset() * 60 * 1000;
  7733. }
  7734. // Validate the extremes. If it goes beyound the data min or
  7735. // max, use the actual data extreme (#2438).
  7736. if (isMin) {
  7737. if (value > rangeSelector.maxInput.HCTime) {
  7738. value = void 0;
  7739. }
  7740. else if (value < dataMin) {
  7741. value = dataMin;
  7742. }
  7743. }
  7744. else {
  7745. if (value < rangeSelector.minInput.HCTime) {
  7746. value = void 0;
  7747. }
  7748. else if (value > dataMax) {
  7749. value = dataMax;
  7750. }
  7751. }
  7752. // Set the extremes
  7753. if (typeof value !== 'undefined') { // @todo typof undefined
  7754. chartAxis.setExtremes(isMin ? value : chartAxis.min, isMin ? chartAxis.max : value, void 0, void 0, { trigger: 'rangeSelectorInput' });
  7755. }
  7756. }
  7757. }
  7758. }
  7759. // Create the text label
  7760. this[name + 'Label'] = label = renderer
  7761. .label(lang[isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo'], this.inputGroup.offset)
  7762. .addClass('highcharts-range-label')
  7763. .attr({
  7764. padding: 2
  7765. })
  7766. .add(inputGroup);
  7767. inputGroup.offset += label.width + 5;
  7768. // Create an SVG label that shows updated date ranges and and records
  7769. // click events that bring in the HTML input.
  7770. this[name + 'DateBox'] = dateBox = renderer
  7771. .label('', inputGroup.offset)
  7772. .addClass('highcharts-range-input')
  7773. .attr({
  7774. padding: 2,
  7775. width: options.inputBoxWidth || 90,
  7776. height: options.inputBoxHeight || 17,
  7777. 'text-align': 'center'
  7778. })
  7779. .on('click', function () {
  7780. // If it is already focused, the onfocus event doesn't fire
  7781. // (#3713)
  7782. rangeSelector.showInput(name);
  7783. rangeSelector[name + 'Input'].focus();
  7784. });
  7785. if (!chart.styledMode) {
  7786. dateBox.attr({
  7787. stroke: options.inputBoxBorderColor || '#cccccc',
  7788. 'stroke-width': 1
  7789. });
  7790. }
  7791. dateBox.add(inputGroup);
  7792. inputGroup.offset += dateBox.width + (isMin ? 10 : 0);
  7793. // Create the HTML input element. This is rendered as 1x1 pixel then set
  7794. // to the right size when focused.
  7795. this[name + 'Input'] = input = createElement('input', {
  7796. name: name,
  7797. className: 'highcharts-range-selector',
  7798. type: 'text'
  7799. }, {
  7800. top: chart.plotTop + 'px' // prevent jump on focus in Firefox
  7801. }, div);
  7802. if (!chart.styledMode) {
  7803. // Styles
  7804. label.css(merge(chartStyle, options.labelStyle));
  7805. dateBox.css(merge({
  7806. color: '#333333'
  7807. }, chartStyle, options.inputStyle));
  7808. css(input, extend({
  7809. position: 'absolute',
  7810. border: 0,
  7811. width: '1px',
  7812. height: '1px',
  7813. padding: 0,
  7814. textAlign: 'center',
  7815. fontSize: chartStyle.fontSize,
  7816. fontFamily: chartStyle.fontFamily,
  7817. top: '-9999em' // #4798
  7818. }, options.inputStyle));
  7819. }
  7820. // Blow up the input box
  7821. input.onfocus = function () {
  7822. rangeSelector.showInput(name);
  7823. };
  7824. // Hide away the input box
  7825. input.onblur = function () {
  7826. // update extermes only when inputs are active
  7827. if (input === H.doc.activeElement) { // Only when focused
  7828. // Update also when no `change` event is triggered, like when
  7829. // clicking inside the SVG (#4710)
  7830. updateExtremes();
  7831. }
  7832. // #10404 - move hide and blur outside focus
  7833. rangeSelector.hideInput(name);
  7834. input.blur(); // #4606
  7835. };
  7836. // handle changes in the input boxes
  7837. input.onchange = updateExtremes;
  7838. input.onkeypress = function (event) {
  7839. // IE does not fire onchange on enter
  7840. if (event.keyCode === 13) {
  7841. updateExtremes();
  7842. }
  7843. };
  7844. };
  7845. /**
  7846. * Get the position of the range selector buttons and inputs. This can be
  7847. * overridden from outside for custom positioning.
  7848. *
  7849. * @private
  7850. * @function Highcharts.RangeSelector#getPosition
  7851. *
  7852. * @return {Highcharts.Dictionary<number>}
  7853. */
  7854. RangeSelector.prototype.getPosition = function () {
  7855. var chart = this.chart, options = chart.options.rangeSelector, top = options.verticalAlign === 'top' ?
  7856. chart.plotTop - chart.axisOffset[0] :
  7857. 0; // set offset only for varticalAlign top
  7858. return {
  7859. buttonTop: top + options.buttonPosition.y,
  7860. inputTop: top + options.inputPosition.y - 10
  7861. };
  7862. };
  7863. /**
  7864. * Get the extremes of YTD. Will choose dataMax if its value is lower than
  7865. * the current timestamp. Will choose dataMin if its value is higher than
  7866. * the timestamp for the start of current year.
  7867. *
  7868. * @private
  7869. * @function Highcharts.RangeSelector#getYTDExtremes
  7870. *
  7871. * @param {number} dataMax
  7872. *
  7873. * @param {number} dataMin
  7874. *
  7875. * @return {*}
  7876. * Returns min and max for the YTD
  7877. */
  7878. RangeSelector.prototype.getYTDExtremes = function (dataMax, dataMin, useUTC) {
  7879. var time = this.chart.time, min, now = new time.Date(dataMax), year = time.get('FullYear', now), startOfYear = useUTC ?
  7880. time.Date.UTC(year, 0, 1) : // eslint-disable-line new-cap
  7881. +new time.Date(year, 0, 1);
  7882. min = Math.max(dataMin || 0, startOfYear);
  7883. now = now.getTime();
  7884. return {
  7885. max: Math.min(dataMax || now, now),
  7886. min: min
  7887. };
  7888. };
  7889. /**
  7890. * Render the range selector including the buttons and the inputs. The first
  7891. * time render is called, the elements are created and positioned. On
  7892. * subsequent calls, they are moved and updated.
  7893. *
  7894. * @private
  7895. * @function Highcharts.RangeSelector#render
  7896. * @param {number} [min]
  7897. * X axis minimum
  7898. * @param {number} [max]
  7899. * X axis maximum
  7900. * @return {void}
  7901. */
  7902. RangeSelector.prototype.render = function (min, max) {
  7903. var rangeSelector = this, chart = rangeSelector.chart, renderer = chart.renderer, container = chart.container, chartOptions = chart.options, navButtonOptions = (chartOptions.exporting &&
  7904. chartOptions.exporting.enabled !== false &&
  7905. chartOptions.navigation &&
  7906. chartOptions.navigation.buttonOptions), lang = defaultOptions.lang, div = rangeSelector.div, options = chartOptions.rangeSelector,
  7907. // Place inputs above the container
  7908. inputsZIndex = pick(chartOptions.chart.style &&
  7909. chartOptions.chart.style.zIndex, 0) + 1, floating = options.floating, buttons = rangeSelector.buttons, inputGroup = rangeSelector.inputGroup, buttonTheme = options.buttonTheme, buttonPosition = options.buttonPosition, inputPosition = options.inputPosition, inputEnabled = options.inputEnabled, states = buttonTheme && buttonTheme.states, plotLeft = chart.plotLeft, buttonLeft, buttonGroup = rangeSelector.buttonGroup, group, groupHeight, rendered = rangeSelector.rendered, verticalAlign = rangeSelector.options.verticalAlign, legend = chart.legend, legendOptions = legend && legend.options, buttonPositionY = buttonPosition.y, inputPositionY = inputPosition.y, animate = chart.hasLoaded, verb = animate ? 'animate' : 'attr', exportingX = 0, alignTranslateY, legendHeight, minPosition, translateY = 0, translateX;
  7910. if (options.enabled === false) {
  7911. return;
  7912. }
  7913. // create the elements
  7914. if (!rendered) {
  7915. rangeSelector.group = group = renderer.g('range-selector-group')
  7916. .attr({
  7917. zIndex: 7
  7918. })
  7919. .add();
  7920. rangeSelector.buttonGroup = buttonGroup =
  7921. renderer.g('range-selector-buttons').add(group);
  7922. rangeSelector.zoomText = renderer
  7923. .text(lang.rangeSelectorZoom, 0, 15)
  7924. .add(buttonGroup);
  7925. if (!chart.styledMode) {
  7926. rangeSelector.zoomText.css(options.labelStyle);
  7927. buttonTheme['stroke-width'] =
  7928. pick(buttonTheme['stroke-width'], 0);
  7929. }
  7930. rangeSelector.buttonOptions.forEach(function (rangeOptions, i) {
  7931. buttons[i] = renderer
  7932. .button(rangeOptions.text, 0, 0, function (e) {
  7933. // extract events from button object and call
  7934. var buttonEvents = (rangeOptions.events &&
  7935. rangeOptions.events.click), callDefaultEvent;
  7936. if (buttonEvents) {
  7937. callDefaultEvent =
  7938. buttonEvents.call(rangeOptions, e);
  7939. }
  7940. if (callDefaultEvent !== false) {
  7941. rangeSelector.clickButton(i);
  7942. }
  7943. rangeSelector.isActive = true;
  7944. }, buttonTheme, states && states.hover, states && states.select, states && states.disabled)
  7945. .attr({
  7946. 'text-align': 'center'
  7947. })
  7948. .add(buttonGroup);
  7949. });
  7950. // first create a wrapper outside the container in order to make
  7951. // the inputs work and make export correct
  7952. if (inputEnabled !== false) {
  7953. rangeSelector.div = div = createElement('div', null, {
  7954. position: 'relative',
  7955. height: 0,
  7956. zIndex: inputsZIndex
  7957. });
  7958. container.parentNode.insertBefore(div, container);
  7959. // Create the group to keep the inputs
  7960. rangeSelector.inputGroup = inputGroup =
  7961. renderer.g('input-group').add(group);
  7962. inputGroup.offset = 0;
  7963. rangeSelector.drawInput('min');
  7964. rangeSelector.drawInput('max');
  7965. }
  7966. }
  7967. // #8769, allow dynamically updating margins
  7968. rangeSelector.zoomText[verb]({
  7969. x: pick(plotLeft + buttonPosition.x, plotLeft)
  7970. });
  7971. // button start position
  7972. buttonLeft = pick(plotLeft + buttonPosition.x, plotLeft) +
  7973. rangeSelector.zoomText.getBBox().width + 5;
  7974. rangeSelector.buttonOptions.forEach(function (rangeOptions, i) {
  7975. buttons[i][verb]({ x: buttonLeft });
  7976. // increase button position for the next button
  7977. buttonLeft += buttons[i].width + pick(options.buttonSpacing, 5);
  7978. });
  7979. plotLeft = chart.plotLeft - chart.spacing[3];
  7980. rangeSelector.updateButtonStates();
  7981. // detect collisiton with exporting
  7982. if (navButtonOptions &&
  7983. this.titleCollision(chart) &&
  7984. verticalAlign === 'top' &&
  7985. buttonPosition.align === 'right' && ((buttonPosition.y +
  7986. buttonGroup.getBBox().height - 12) <
  7987. ((navButtonOptions.y || 0) +
  7988. navButtonOptions.height))) {
  7989. exportingX = -40;
  7990. }
  7991. translateX = buttonPosition.x - chart.spacing[3];
  7992. if (buttonPosition.align === 'right') {
  7993. translateX += exportingX - plotLeft; // (#13014)
  7994. }
  7995. else if (buttonPosition.align === 'center') {
  7996. translateX -= plotLeft / 2;
  7997. }
  7998. // align button group
  7999. buttonGroup.align({
  8000. y: buttonPosition.y,
  8001. width: buttonGroup.getBBox().width,
  8002. align: buttonPosition.align,
  8003. x: translateX
  8004. }, true, chart.spacingBox);
  8005. // skip animation
  8006. rangeSelector.group.placed = animate;
  8007. rangeSelector.buttonGroup.placed = animate;
  8008. if (inputEnabled !== false) {
  8009. var inputGroupX, inputGroupWidth, buttonGroupX, buttonGroupWidth;
  8010. // detect collision with exporting
  8011. if (navButtonOptions &&
  8012. this.titleCollision(chart) &&
  8013. verticalAlign === 'top' &&
  8014. inputPosition.align === 'right' && ((inputPosition.y -
  8015. inputGroup.getBBox().height - 12) <
  8016. ((navButtonOptions.y || 0) +
  8017. navButtonOptions.height +
  8018. chart.spacing[0]))) {
  8019. exportingX = -40;
  8020. }
  8021. else {
  8022. exportingX = 0;
  8023. }
  8024. if (inputPosition.align === 'left') {
  8025. translateX = plotLeft;
  8026. }
  8027. else if (inputPosition.align === 'right') {
  8028. translateX = -Math.max(chart.axisOffset[1], -exportingX);
  8029. }
  8030. // Update the alignment to the updated spacing box
  8031. inputGroup.align({
  8032. y: inputPosition.y,
  8033. width: inputGroup.getBBox().width,
  8034. align: inputPosition.align,
  8035. // fix wrong getBBox() value on right align
  8036. x: inputPosition.x + translateX - 2
  8037. }, true, chart.spacingBox);
  8038. // detect collision
  8039. inputGroupX = (inputGroup.alignAttr.translateX +
  8040. inputGroup.alignOptions.x -
  8041. exportingX +
  8042. // getBBox for detecing left margin
  8043. inputGroup.getBBox().x +
  8044. // 2px padding to not overlap input and label
  8045. 2);
  8046. inputGroupWidth = inputGroup.alignOptions.width;
  8047. buttonGroupX = buttonGroup.alignAttr.translateX +
  8048. buttonGroup.getBBox().x;
  8049. // 20 is minimal spacing between elements
  8050. buttonGroupWidth = buttonGroup.getBBox().width + 20;
  8051. if ((inputPosition.align ===
  8052. buttonPosition.align) || ((buttonGroupX + buttonGroupWidth > inputGroupX) &&
  8053. (inputGroupX + inputGroupWidth > buttonGroupX) &&
  8054. (buttonPositionY <
  8055. (inputPositionY +
  8056. inputGroup.getBBox().height)))) {
  8057. inputGroup.attr({
  8058. translateX: inputGroup.alignAttr.translateX +
  8059. (chart.axisOffset[1] >= -exportingX ? 0 : -exportingX),
  8060. translateY: inputGroup.alignAttr.translateY +
  8061. buttonGroup.getBBox().height + 10
  8062. });
  8063. }
  8064. // Set or reset the input values
  8065. rangeSelector.setInputValue('min', min);
  8066. rangeSelector.setInputValue('max', max);
  8067. // skip animation
  8068. rangeSelector.inputGroup.placed = animate;
  8069. }
  8070. // vertical align
  8071. rangeSelector.group.align({
  8072. verticalAlign: verticalAlign
  8073. }, true, chart.spacingBox);
  8074. // set position
  8075. groupHeight =
  8076. rangeSelector.group.getBBox().height + 20; // # 20 padding
  8077. alignTranslateY =
  8078. rangeSelector.group.alignAttr.translateY;
  8079. // calculate bottom position
  8080. if (verticalAlign === 'bottom') {
  8081. legendHeight = (legendOptions &&
  8082. legendOptions.verticalAlign === 'bottom' &&
  8083. legendOptions.enabled &&
  8084. !legendOptions.floating ?
  8085. legend.legendHeight + pick(legendOptions.margin, 10) :
  8086. 0);
  8087. groupHeight = groupHeight + legendHeight - 20;
  8088. translateY = (alignTranslateY -
  8089. groupHeight -
  8090. (floating ? 0 : options.y) -
  8091. (chart.titleOffset ? chart.titleOffset[2] : 0) -
  8092. 10 // 10 spacing
  8093. );
  8094. }
  8095. if (verticalAlign === 'top') {
  8096. if (floating) {
  8097. translateY = 0;
  8098. }
  8099. if (chart.titleOffset && chart.titleOffset[0]) {
  8100. translateY = chart.titleOffset[0];
  8101. }
  8102. translateY += ((chart.margin[0] - chart.spacing[0]) || 0);
  8103. }
  8104. else if (verticalAlign === 'middle') {
  8105. if (inputPositionY === buttonPositionY) {
  8106. if (inputPositionY < 0) {
  8107. translateY = alignTranslateY + minPosition;
  8108. }
  8109. else {
  8110. translateY = alignTranslateY;
  8111. }
  8112. }
  8113. else if (inputPositionY || buttonPositionY) {
  8114. if (inputPositionY < 0 ||
  8115. buttonPositionY < 0) {
  8116. translateY -= Math.min(inputPositionY, buttonPositionY);
  8117. }
  8118. else {
  8119. translateY =
  8120. alignTranslateY - groupHeight + minPosition;
  8121. }
  8122. }
  8123. }
  8124. rangeSelector.group.translate(options.x, options.y + Math.floor(translateY));
  8125. // translate HTML inputs
  8126. if (inputEnabled !== false) {
  8127. rangeSelector.minInput.style.marginTop =
  8128. rangeSelector.group.translateY + 'px';
  8129. rangeSelector.maxInput.style.marginTop =
  8130. rangeSelector.group.translateY + 'px';
  8131. }
  8132. rangeSelector.rendered = true;
  8133. };
  8134. /**
  8135. * Extracts height of range selector
  8136. *
  8137. * @private
  8138. * @function Highcharts.RangeSelector#getHeight
  8139. * @return {number}
  8140. * Returns rangeSelector height
  8141. */
  8142. RangeSelector.prototype.getHeight = function () {
  8143. var rangeSelector = this, options = rangeSelector.options, rangeSelectorGroup = rangeSelector.group, inputPosition = options.inputPosition, buttonPosition = options.buttonPosition, yPosition = options.y, buttonPositionY = buttonPosition.y, inputPositionY = inputPosition.y, rangeSelectorHeight = 0, minPosition;
  8144. if (options.height) {
  8145. return options.height;
  8146. }
  8147. rangeSelectorHeight = rangeSelectorGroup ?
  8148. // 13px to keep back compatibility
  8149. (rangeSelectorGroup.getBBox(true).height) + 13 +
  8150. yPosition :
  8151. 0;
  8152. minPosition = Math.min(inputPositionY, buttonPositionY);
  8153. if ((inputPositionY < 0 && buttonPositionY < 0) ||
  8154. (inputPositionY > 0 && buttonPositionY > 0)) {
  8155. rangeSelectorHeight += Math.abs(minPosition);
  8156. }
  8157. return rangeSelectorHeight;
  8158. };
  8159. /**
  8160. * Detect collision with title or subtitle
  8161. *
  8162. * @private
  8163. * @function Highcharts.RangeSelector#titleCollision
  8164. *
  8165. * @param {Highcharts.Chart} chart
  8166. *
  8167. * @return {boolean}
  8168. * Returns collision status
  8169. */
  8170. RangeSelector.prototype.titleCollision = function (chart) {
  8171. return !(chart.options.title.text ||
  8172. chart.options.subtitle.text);
  8173. };
  8174. /**
  8175. * Update the range selector with new options
  8176. *
  8177. * @private
  8178. * @function Highcharts.RangeSelector#update
  8179. * @param {Highcharts.RangeSelectorOptions} options
  8180. * @return {void}
  8181. */
  8182. RangeSelector.prototype.update = function (options) {
  8183. var chart = this.chart;
  8184. merge(true, chart.options.rangeSelector, options);
  8185. this.destroy();
  8186. this.init(chart);
  8187. chart.rangeSelector.render();
  8188. };
  8189. /**
  8190. * Destroys allocated elements.
  8191. *
  8192. * @private
  8193. * @function Highcharts.RangeSelector#destroy
  8194. */
  8195. RangeSelector.prototype.destroy = function () {
  8196. var rSelector = this, minInput = rSelector.minInput, maxInput = rSelector.maxInput;
  8197. rSelector.unMouseDown();
  8198. rSelector.unResize();
  8199. // Destroy elements in collections
  8200. destroyObjectProperties(rSelector.buttons);
  8201. // Clear input element events
  8202. if (minInput) {
  8203. minInput.onfocus = minInput.onblur = minInput.onchange = null;
  8204. }
  8205. if (maxInput) {
  8206. maxInput.onfocus = maxInput.onblur = maxInput.onchange = null;
  8207. }
  8208. // Destroy HTML and SVG elements
  8209. objectEach(rSelector, function (val, key) {
  8210. if (val && key !== 'chart') {
  8211. if (val instanceof SVGElement) {
  8212. // SVGElement
  8213. val.destroy();
  8214. }
  8215. else if (val instanceof window.HTMLElement) {
  8216. // HTML element
  8217. discardElement(val);
  8218. }
  8219. }
  8220. if (val !== RangeSelector.prototype[key]) {
  8221. rSelector[key] = null;
  8222. }
  8223. }, this);
  8224. };
  8225. return RangeSelector;
  8226. }());
  8227. /**
  8228. * The default buttons for pre-selecting time frames
  8229. */
  8230. RangeSelector.prototype.defaultButtons = [{
  8231. type: 'month',
  8232. count: 1,
  8233. text: '1m'
  8234. }, {
  8235. type: 'month',
  8236. count: 3,
  8237. text: '3m'
  8238. }, {
  8239. type: 'month',
  8240. count: 6,
  8241. text: '6m'
  8242. }, {
  8243. type: 'ytd',
  8244. text: 'YTD'
  8245. }, {
  8246. type: 'year',
  8247. count: 1,
  8248. text: '1y'
  8249. }, {
  8250. type: 'all',
  8251. text: 'All'
  8252. }];
  8253. /**
  8254. * Get the axis min value based on the range option and the current max. For
  8255. * stock charts this is extended via the {@link RangeSelector} so that if the
  8256. * selected range is a multiple of months or years, it is compensated for
  8257. * various month lengths.
  8258. *
  8259. * @private
  8260. * @function Highcharts.Axis#minFromRange
  8261. * @return {number|undefined}
  8262. * The new minimum value.
  8263. */
  8264. Axis.prototype.minFromRange = function () {
  8265. var rangeOptions = this.range, type = rangeOptions.type, min, max = this.max, dataMin, range, time = this.chart.time,
  8266. // Get the true range from a start date
  8267. getTrueRange = function (base, count) {
  8268. var timeName = type === 'year' ? 'FullYear' : 'Month';
  8269. var date = new time.Date(base);
  8270. var basePeriod = time.get(timeName, date);
  8271. time.set(timeName, date, basePeriod + count);
  8272. if (basePeriod === time.get(timeName, date)) {
  8273. time.set('Date', date, 0); // #6537
  8274. }
  8275. return date.getTime() - base;
  8276. };
  8277. if (isNumber(rangeOptions)) {
  8278. min = max - rangeOptions;
  8279. range = rangeOptions;
  8280. }
  8281. else {
  8282. min = max + getTrueRange(max, -rangeOptions.count);
  8283. // Let the fixedRange reflect initial settings (#5930)
  8284. if (this.chart) {
  8285. this.chart.fixedRange = max - min;
  8286. }
  8287. }
  8288. dataMin = pick(this.dataMin, Number.MIN_VALUE);
  8289. if (!isNumber(min)) {
  8290. min = dataMin;
  8291. }
  8292. if (min <= dataMin) {
  8293. min = dataMin;
  8294. if (typeof range === 'undefined') { // #4501
  8295. range = getTrueRange(min, rangeOptions.count);
  8296. }
  8297. this.newMax = Math.min(min + range, this.dataMax);
  8298. }
  8299. if (!isNumber(max)) {
  8300. min = void 0;
  8301. }
  8302. return min;
  8303. };
  8304. if (!H.RangeSelector) {
  8305. // Initialize rangeselector for stock charts
  8306. addEvent(Chart, 'afterGetContainer', function () {
  8307. if (this.options.rangeSelector.enabled) {
  8308. this.rangeSelector = new RangeSelector(this);
  8309. }
  8310. });
  8311. addEvent(Chart, 'beforeRender', function () {
  8312. var chart = this, axes = chart.axes, rangeSelector = chart.rangeSelector, verticalAlign;
  8313. if (rangeSelector) {
  8314. if (isNumber(rangeSelector.deferredYTDClick)) {
  8315. rangeSelector.clickButton(rangeSelector.deferredYTDClick);
  8316. delete rangeSelector.deferredYTDClick;
  8317. }
  8318. axes.forEach(function (axis) {
  8319. axis.updateNames();
  8320. axis.setScale();
  8321. });
  8322. chart.getAxisMargins();
  8323. rangeSelector.render();
  8324. verticalAlign = rangeSelector.options.verticalAlign;
  8325. if (!rangeSelector.options.floating) {
  8326. if (verticalAlign === 'bottom') {
  8327. this.extraBottomMargin = true;
  8328. }
  8329. else if (verticalAlign !== 'middle') {
  8330. this.extraTopMargin = true;
  8331. }
  8332. }
  8333. }
  8334. });
  8335. addEvent(Chart, 'update', function (e) {
  8336. var chart = this, options = e.options, optionsRangeSelector = options.rangeSelector, rangeSelector = chart.rangeSelector, verticalAlign, extraBottomMarginWas = this.extraBottomMargin, extraTopMarginWas = this.extraTopMargin;
  8337. if (optionsRangeSelector &&
  8338. optionsRangeSelector.enabled &&
  8339. !defined(rangeSelector)) {
  8340. this.options.rangeSelector.enabled = true;
  8341. this.rangeSelector = new RangeSelector(this);
  8342. }
  8343. this.extraBottomMargin = false;
  8344. this.extraTopMargin = false;
  8345. if (rangeSelector) {
  8346. rangeSelector.render();
  8347. verticalAlign = (optionsRangeSelector &&
  8348. optionsRangeSelector.verticalAlign) || (rangeSelector.options && rangeSelector.options.verticalAlign);
  8349. if (!rangeSelector.options.floating) {
  8350. if (verticalAlign === 'bottom') {
  8351. this.extraBottomMargin = true;
  8352. }
  8353. else if (verticalAlign !== 'middle') {
  8354. this.extraTopMargin = true;
  8355. }
  8356. }
  8357. if (this.extraBottomMargin !== extraBottomMarginWas ||
  8358. this.extraTopMargin !== extraTopMarginWas) {
  8359. this.isDirtyBox = true;
  8360. }
  8361. }
  8362. });
  8363. addEvent(Chart, 'render', function () {
  8364. var chart = this, rangeSelector = chart.rangeSelector, verticalAlign;
  8365. if (rangeSelector && !rangeSelector.options.floating) {
  8366. rangeSelector.render();
  8367. verticalAlign = rangeSelector.options.verticalAlign;
  8368. if (verticalAlign === 'bottom') {
  8369. this.extraBottomMargin = true;
  8370. }
  8371. else if (verticalAlign !== 'middle') {
  8372. this.extraTopMargin = true;
  8373. }
  8374. }
  8375. });
  8376. addEvent(Chart, 'getMargins', function () {
  8377. var rangeSelector = this.rangeSelector, rangeSelectorHeight;
  8378. if (rangeSelector) {
  8379. rangeSelectorHeight = rangeSelector.getHeight();
  8380. if (this.extraTopMargin) {
  8381. this.plotTop += rangeSelectorHeight;
  8382. }
  8383. if (this.extraBottomMargin) {
  8384. this.marginBottom += rangeSelectorHeight;
  8385. }
  8386. }
  8387. });
  8388. Chart.prototype.callbacks.push(function (chart) {
  8389. var extremes, rangeSelector = chart.rangeSelector, unbindRender, unbindSetExtremes, legend, alignTo, verticalAlign;
  8390. /**
  8391. * @private
  8392. */
  8393. function renderRangeSelector() {
  8394. extremes = chart.xAxis[0].getExtremes();
  8395. legend = chart.legend;
  8396. verticalAlign = rangeSelector === null || rangeSelector === void 0 ? void 0 : rangeSelector.options.verticalAlign;
  8397. if (isNumber(extremes.min)) {
  8398. rangeSelector.render(extremes.min, extremes.max);
  8399. }
  8400. // Re-align the legend so that it's below the rangeselector
  8401. if (rangeSelector && legend.display &&
  8402. verticalAlign === 'top' &&
  8403. verticalAlign === legend.options.verticalAlign) {
  8404. // Create a new alignment box for the legend.
  8405. alignTo = merge(chart.spacingBox);
  8406. if (legend.options.layout === 'vertical') {
  8407. alignTo.y = chart.plotTop;
  8408. }
  8409. else {
  8410. alignTo.y += rangeSelector.getHeight();
  8411. }
  8412. legend.group.placed = false; // Don't animate the alignment.
  8413. legend.align(alignTo);
  8414. }
  8415. }
  8416. if (rangeSelector) {
  8417. // redraw the scroller on setExtremes
  8418. unbindSetExtremes = addEvent(chart.xAxis[0], 'afterSetExtremes', function (e) {
  8419. rangeSelector.render(e.min, e.max);
  8420. });
  8421. // redraw the scroller chart resize
  8422. unbindRender = addEvent(chart, 'redraw', renderRangeSelector);
  8423. // do it now
  8424. renderRangeSelector();
  8425. }
  8426. // Remove resize/afterSetExtremes at chart destroy
  8427. addEvent(chart, 'destroy', function destroyEvents() {
  8428. if (rangeSelector) {
  8429. unbindRender();
  8430. unbindSetExtremes();
  8431. }
  8432. });
  8433. });
  8434. H.RangeSelector = RangeSelector;
  8435. }
  8436. return H.RangeSelector;
  8437. });
  8438. _registerModule(_modules, 'parts/NavigatorAxis.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
  8439. /* *
  8440. *
  8441. * (c) 2010-2020 Torstein Honsi
  8442. *
  8443. * License: www.highcharts.com/license
  8444. *
  8445. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8446. *
  8447. * */
  8448. var isTouchDevice = H.isTouchDevice;
  8449. var addEvent = U.addEvent, correctFloat = U.correctFloat, defined = U.defined, isNumber = U.isNumber, pick = U.pick;
  8450. /* eslint-disable valid-jsdoc */
  8451. /**
  8452. * @private
  8453. * @class
  8454. */
  8455. var NavigatorAxisAdditions = /** @class */ (function () {
  8456. /* *
  8457. *
  8458. * Constructors
  8459. *
  8460. * */
  8461. function NavigatorAxisAdditions(axis) {
  8462. this.axis = axis;
  8463. }
  8464. /* *
  8465. *
  8466. * Functions
  8467. *
  8468. * */
  8469. /**
  8470. * @private
  8471. */
  8472. NavigatorAxisAdditions.prototype.destroy = function () {
  8473. this.axis = void 0;
  8474. };
  8475. /**
  8476. * Add logic to normalize the zoomed range in order to preserve the pressed
  8477. * state of range selector buttons
  8478. *
  8479. * @private
  8480. * @function Highcharts.Axis#toFixedRange
  8481. * @param {number} [pxMin]
  8482. * @param {number} [pxMax]
  8483. * @param {number} [fixedMin]
  8484. * @param {number} [fixedMax]
  8485. * @return {*}
  8486. */
  8487. NavigatorAxisAdditions.prototype.toFixedRange = function (pxMin, pxMax, fixedMin, fixedMax) {
  8488. var navigator = this;
  8489. var axis = navigator.axis;
  8490. var chart = axis.chart;
  8491. var fixedRange = chart && chart.fixedRange, halfPointRange = (axis.pointRange || 0) / 2, newMin = pick(fixedMin, axis.translate(pxMin, true, !axis.horiz)), newMax = pick(fixedMax, axis.translate(pxMax, true, !axis.horiz)), changeRatio = fixedRange && (newMax - newMin) / fixedRange;
  8492. // Add/remove half point range to/from the extremes (#1172)
  8493. if (!defined(fixedMin)) {
  8494. newMin = correctFloat(newMin + halfPointRange);
  8495. }
  8496. if (!defined(fixedMax)) {
  8497. newMax = correctFloat(newMax - halfPointRange);
  8498. }
  8499. // If the difference between the fixed range and the actual requested
  8500. // range is too great, the user is dragging across an ordinal gap, and
  8501. // we need to release the range selector button.
  8502. if (changeRatio > 0.7 && changeRatio < 1.3) {
  8503. if (fixedMax) {
  8504. newMin = newMax - fixedRange;
  8505. }
  8506. else {
  8507. newMax = newMin + fixedRange;
  8508. }
  8509. }
  8510. if (!isNumber(newMin) || !isNumber(newMax)) { // #1195, #7411
  8511. newMin = newMax = void 0;
  8512. }
  8513. return {
  8514. min: newMin,
  8515. max: newMax
  8516. };
  8517. };
  8518. return NavigatorAxisAdditions;
  8519. }());
  8520. /**
  8521. * @private
  8522. * @class
  8523. */
  8524. var NavigatorAxis = /** @class */ (function () {
  8525. function NavigatorAxis() {
  8526. }
  8527. /* *
  8528. *
  8529. * Static Functions
  8530. *
  8531. * */
  8532. /**
  8533. * @private
  8534. */
  8535. NavigatorAxis.compose = function (AxisClass) {
  8536. AxisClass.keepProps.push('navigatorAxis');
  8537. /* eslint-disable no-invalid-this */
  8538. addEvent(AxisClass, 'init', function () {
  8539. var axis = this;
  8540. if (!axis.navigatorAxis) {
  8541. axis.navigatorAxis = new NavigatorAxisAdditions(axis);
  8542. }
  8543. });
  8544. // For Stock charts, override selection zooming with some special
  8545. // features because X axis zooming is already allowed by the Navigator
  8546. // and Range selector.
  8547. addEvent(AxisClass, 'zoom', function (e) {
  8548. var axis = this;
  8549. var chart = axis.chart;
  8550. var chartOptions = chart.options;
  8551. var navigator = chartOptions.navigator;
  8552. var navigatorAxis = axis.navigatorAxis;
  8553. var pinchType = chartOptions.chart.pinchType;
  8554. var rangeSelector = chartOptions.rangeSelector;
  8555. var zoomType = chartOptions.chart.zoomType;
  8556. var previousZoom;
  8557. if (axis.isXAxis && ((navigator && navigator.enabled) ||
  8558. (rangeSelector && rangeSelector.enabled))) {
  8559. // For y only zooming, ignore the X axis completely
  8560. if (zoomType === 'y') {
  8561. e.zoomed = false;
  8562. // For xy zooming, record the state of the zoom before zoom
  8563. // selection, then when the reset button is pressed, revert to
  8564. // this state. This should apply only if the chart is
  8565. // initialized with a range (#6612), otherwise zoom all the way
  8566. // out.
  8567. }
  8568. else if (((!isTouchDevice && zoomType === 'xy') ||
  8569. (isTouchDevice && pinchType === 'xy')) &&
  8570. axis.options.range) {
  8571. previousZoom = navigatorAxis.previousZoom;
  8572. if (defined(e.newMin)) {
  8573. navigatorAxis.previousZoom = [axis.min, axis.max];
  8574. }
  8575. else if (previousZoom) {
  8576. e.newMin = previousZoom[0];
  8577. e.newMax = previousZoom[1];
  8578. navigatorAxis.previousZoom = void 0;
  8579. }
  8580. }
  8581. }
  8582. if (typeof e.zoomed !== 'undefined') {
  8583. e.preventDefault();
  8584. }
  8585. });
  8586. /* eslint-enable no-invalid-this */
  8587. };
  8588. /* *
  8589. *
  8590. * Static Properties
  8591. *
  8592. * */
  8593. /**
  8594. * @private
  8595. */
  8596. NavigatorAxis.AdditionsClass = NavigatorAxisAdditions;
  8597. return NavigatorAxis;
  8598. }());
  8599. return NavigatorAxis;
  8600. });
  8601. _registerModule(_modules, 'parts/Navigator.js', [_modules['parts/Axis.js'], _modules['parts/Chart.js'], _modules['parts/Color.js'], _modules['parts/Globals.js'], _modules['parts/NavigatorAxis.js'], _modules['parts/Options.js'], _modules['parts/Scrollbar.js'], _modules['parts/Utilities.js']], function (Axis, Chart, Color, H, NavigatorAxis, O, Scrollbar, U) {
  8602. /* *
  8603. *
  8604. * (c) 2010-2020 Torstein Honsi
  8605. *
  8606. * License: www.highcharts.com/license
  8607. *
  8608. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8609. *
  8610. * */
  8611. var color = Color.parse;
  8612. var defaultOptions = O.defaultOptions;
  8613. var addEvent = U.addEvent, clamp = U.clamp, correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, erase = U.erase, extend = U.extend, find = U.find, isArray = U.isArray, isNumber = U.isNumber, merge = U.merge, pick = U.pick, removeEvent = U.removeEvent, splat = U.splat;
  8614. var hasTouch = H.hasTouch, isTouchDevice = H.isTouchDevice, Series = H.Series, seriesTypes = H.seriesTypes, defaultSeriesType,
  8615. // Finding the min or max of a set of variables where we don't know if they
  8616. // are defined, is a pattern that is repeated several places in Highcharts.
  8617. // Consider making this a global utility method.
  8618. numExt = function (extreme) {
  8619. var args = [];
  8620. for (var _i = 1; _i < arguments.length; _i++) {
  8621. args[_i - 1] = arguments[_i];
  8622. }
  8623. var numbers = [].filter.call(args, isNumber);
  8624. if (numbers.length) {
  8625. return Math[extreme].apply(0, numbers);
  8626. }
  8627. };
  8628. defaultSeriesType = typeof seriesTypes.areaspline === 'undefined' ?
  8629. 'line' :
  8630. 'areaspline';
  8631. extend(defaultOptions, {
  8632. /**
  8633. * Maximum range which can be set using the navigator's handles.
  8634. * Opposite of [xAxis.minRange](#xAxis.minRange).
  8635. *
  8636. * @sample {highstock} stock/navigator/maxrange/
  8637. * Defined max and min range
  8638. *
  8639. * @type {number}
  8640. * @since 6.0.0
  8641. * @product highstock gantt
  8642. * @apioption xAxis.maxRange
  8643. */
  8644. /**
  8645. * The navigator is a small series below the main series, displaying
  8646. * a view of the entire data set. It provides tools to zoom in and
  8647. * out on parts of the data as well as panning across the dataset.
  8648. *
  8649. * @product highstock gantt
  8650. * @optionparent navigator
  8651. */
  8652. navigator: {
  8653. /**
  8654. * Whether the navigator and scrollbar should adapt to updated data
  8655. * in the base X axis. When loading data async, as in the demo below,
  8656. * this should be `false`. Otherwise new data will trigger navigator
  8657. * redraw, which will cause unwanted looping. In the demo below, the
  8658. * data in the navigator is set only once. On navigating, only the main
  8659. * chart content is updated.
  8660. *
  8661. * @sample {highstock} stock/demo/lazy-loading/
  8662. * Set to false with async data loading
  8663. *
  8664. * @type {boolean}
  8665. * @default true
  8666. * @apioption navigator.adaptToUpdatedData
  8667. */
  8668. /**
  8669. * An integer identifying the index to use for the base series, or a
  8670. * string representing the id of the series.
  8671. *
  8672. * **Note**: As of Highcharts 5.0, this is now a deprecated option.
  8673. * Prefer [series.showInNavigator](#plotOptions.series.showInNavigator).
  8674. *
  8675. * @see [series.showInNavigator](#plotOptions.series.showInNavigator)
  8676. *
  8677. * @deprecated
  8678. * @type {number|string}
  8679. * @default 0
  8680. * @apioption navigator.baseSeries
  8681. */
  8682. /**
  8683. * Enable or disable the navigator.
  8684. *
  8685. * @sample {highstock} stock/navigator/enabled/
  8686. * Disable the navigator
  8687. *
  8688. * @type {boolean}
  8689. * @default true
  8690. * @apioption navigator.enabled
  8691. */
  8692. /**
  8693. * When the chart is inverted, whether to draw the navigator on the
  8694. * opposite side.
  8695. *
  8696. * @type {boolean}
  8697. * @default false
  8698. * @since 5.0.8
  8699. * @apioption navigator.opposite
  8700. */
  8701. /**
  8702. * The height of the navigator.
  8703. *
  8704. * @sample {highstock} stock/navigator/height/
  8705. * A higher navigator
  8706. */
  8707. height: 40,
  8708. /**
  8709. * The distance from the nearest element, the X axis or X axis labels.
  8710. *
  8711. * @sample {highstock} stock/navigator/margin/
  8712. * A margin of 2 draws the navigator closer to the X axis labels
  8713. */
  8714. margin: 25,
  8715. /**
  8716. * Whether the mask should be inside the range marking the zoomed
  8717. * range, or outside. In Highstock 1.x it was always `false`.
  8718. *
  8719. * @sample {highstock} stock/navigator/maskinside-false/
  8720. * False, mask outside
  8721. *
  8722. * @since 2.0
  8723. */
  8724. maskInside: true,
  8725. /**
  8726. * Options for the handles for dragging the zoomed area.
  8727. *
  8728. * @sample {highstock} stock/navigator/handles/
  8729. * Colored handles
  8730. */
  8731. handles: {
  8732. /**
  8733. * Width for handles.
  8734. *
  8735. * @sample {highstock} stock/navigator/styled-handles/
  8736. * Styled handles
  8737. *
  8738. * @since 6.0.0
  8739. */
  8740. width: 7,
  8741. /**
  8742. * Height for handles.
  8743. *
  8744. * @sample {highstock} stock/navigator/styled-handles/
  8745. * Styled handles
  8746. *
  8747. * @since 6.0.0
  8748. */
  8749. height: 15,
  8750. /**
  8751. * Array to define shapes of handles. 0-index for left, 1-index for
  8752. * right.
  8753. *
  8754. * Additionally, the URL to a graphic can be given on this form:
  8755. * `url(graphic.png)`. Note that for the image to be applied to
  8756. * exported charts, its URL needs to be accessible by the export
  8757. * server.
  8758. *
  8759. * Custom callbacks for symbol path generation can also be added to
  8760. * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
  8761. * used by its method name, as shown in the demo.
  8762. *
  8763. * @sample {highstock} stock/navigator/styled-handles/
  8764. * Styled handles
  8765. *
  8766. * @type {Array<string>}
  8767. * @default ["navigator-handle", "navigator-handle"]
  8768. * @since 6.0.0
  8769. */
  8770. symbols: ['navigator-handle', 'navigator-handle'],
  8771. /**
  8772. * Allows to enable/disable handles.
  8773. *
  8774. * @since 6.0.0
  8775. */
  8776. enabled: true,
  8777. /**
  8778. * The width for the handle border and the stripes inside.
  8779. *
  8780. * @sample {highstock} stock/navigator/styled-handles/
  8781. * Styled handles
  8782. *
  8783. * @since 6.0.0
  8784. * @apioption navigator.handles.lineWidth
  8785. */
  8786. lineWidth: 1,
  8787. /**
  8788. * The fill for the handle.
  8789. *
  8790. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  8791. */
  8792. backgroundColor: '#f2f2f2',
  8793. /**
  8794. * The stroke for the handle border and the stripes inside.
  8795. *
  8796. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  8797. */
  8798. borderColor: '#999999'
  8799. },
  8800. /**
  8801. * The color of the mask covering the areas of the navigator series
  8802. * that are currently not visible in the main series. The default
  8803. * color is bluish with an opacity of 0.3 to see the series below.
  8804. *
  8805. * @see In styled mode, the mask is styled with the
  8806. * `.highcharts-navigator-mask` and
  8807. * `.highcharts-navigator-mask-inside` classes.
  8808. *
  8809. * @sample {highstock} stock/navigator/maskfill/
  8810. * Blue, semi transparent mask
  8811. *
  8812. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  8813. * @default rgba(102,133,194,0.3)
  8814. */
  8815. maskFill: color('#6685c2').setOpacity(0.3).get(),
  8816. /**
  8817. * The color of the line marking the currently zoomed area in the
  8818. * navigator.
  8819. *
  8820. * @sample {highstock} stock/navigator/outline/
  8821. * 2px blue outline
  8822. *
  8823. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  8824. * @default #cccccc
  8825. */
  8826. outlineColor: '#cccccc',
  8827. /**
  8828. * The width of the line marking the currently zoomed area in the
  8829. * navigator.
  8830. *
  8831. * @see In styled mode, the outline stroke width is set with the
  8832. * `.highcharts-navigator-outline` class.
  8833. *
  8834. * @sample {highstock} stock/navigator/outline/
  8835. * 2px blue outline
  8836. *
  8837. * @type {number}
  8838. */
  8839. outlineWidth: 1,
  8840. /**
  8841. * Options for the navigator series. Available options are the same
  8842. * as any series, documented at [plotOptions](#plotOptions.series)
  8843. * and [series](#series).
  8844. *
  8845. * Unless data is explicitly defined on navigator.series, the data
  8846. * is borrowed from the first series in the chart.
  8847. *
  8848. * Default series options for the navigator series are:
  8849. * ```js
  8850. * series: {
  8851. * type: 'areaspline',
  8852. * fillOpacity: 0.05,
  8853. * dataGrouping: {
  8854. * smoothed: true
  8855. * },
  8856. * lineWidth: 1,
  8857. * marker: {
  8858. * enabled: false
  8859. * }
  8860. * }
  8861. * ```
  8862. *
  8863. * @see In styled mode, the navigator series is styled with the
  8864. * `.highcharts-navigator-series` class.
  8865. *
  8866. * @sample {highstock} stock/navigator/series-data/
  8867. * Using a separate data set for the navigator
  8868. * @sample {highstock} stock/navigator/series/
  8869. * A green navigator series
  8870. *
  8871. * @type {*|Array<*>|Highcharts.SeriesOptionsType|Array<Highcharts.SeriesOptionsType>}
  8872. */
  8873. series: {
  8874. /**
  8875. * The type of the navigator series. Defaults to `areaspline` if
  8876. * defined, otherwise `line`.
  8877. *
  8878. * Heads up:
  8879. * In column-type navigator, zooming is limited to at least one
  8880. * point with its `pointRange`.
  8881. *
  8882. * @sample {highstock} stock/navigator/column/
  8883. * Column type navigator
  8884. *
  8885. * @type {string}
  8886. * @default areaspline
  8887. */
  8888. type: defaultSeriesType,
  8889. /**
  8890. * The fill opacity of the navigator series.
  8891. */
  8892. fillOpacity: 0.05,
  8893. /**
  8894. * The pixel line width of the navigator series.
  8895. */
  8896. lineWidth: 1,
  8897. /**
  8898. * @ignore-option
  8899. */
  8900. compare: null,
  8901. /**
  8902. * Unless data is explicitly defined, the data is borrowed from the
  8903. * first series in the chart.
  8904. *
  8905. * @type {Array<number|Array<number|string|null>|object|null>}
  8906. * @product highstock
  8907. * @apioption navigator.series.data
  8908. */
  8909. /**
  8910. * Data grouping options for the navigator series.
  8911. *
  8912. * @extends plotOptions.series.dataGrouping
  8913. */
  8914. dataGrouping: {
  8915. approximation: 'average',
  8916. enabled: true,
  8917. groupPixelWidth: 2,
  8918. smoothed: true,
  8919. // Day and week differs from plotOptions.series.dataGrouping
  8920. units: [
  8921. ['millisecond', [1, 2, 5, 10, 20, 25, 50, 100, 200, 500]],
  8922. ['second', [1, 2, 5, 10, 15, 30]],
  8923. ['minute', [1, 2, 5, 10, 15, 30]],
  8924. ['hour', [1, 2, 3, 4, 6, 8, 12]],
  8925. ['day', [1, 2, 3, 4]],
  8926. ['week', [1, 2, 3]],
  8927. ['month', [1, 3, 6]],
  8928. ['year', null]
  8929. ]
  8930. },
  8931. /**
  8932. * Data label options for the navigator series. Data labels are
  8933. * disabled by default on the navigator series.
  8934. *
  8935. * @extends plotOptions.series.dataLabels
  8936. */
  8937. dataLabels: {
  8938. enabled: false,
  8939. zIndex: 2 // #1839
  8940. },
  8941. id: 'highcharts-navigator-series',
  8942. className: 'highcharts-navigator-series',
  8943. /**
  8944. * Sets the fill color of the navigator series.
  8945. *
  8946. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  8947. * @apioption navigator.series.color
  8948. */
  8949. /**
  8950. * Line color for the navigator series. Allows setting the color
  8951. * while disallowing the default candlestick setting.
  8952. *
  8953. * @type {Highcharts.ColorString|null}
  8954. */
  8955. lineColor: null,
  8956. marker: {
  8957. enabled: false
  8958. },
  8959. /**
  8960. * Since Highstock v8, default value is the same as default
  8961. * `pointRange` defined for a specific type (e.g. `null` for
  8962. * column type).
  8963. *
  8964. * In Highstock version < 8, defaults to 0.
  8965. *
  8966. * @extends plotOptions.series.pointRange
  8967. * @type {number|null}
  8968. * @apioption navigator.series.pointRange
  8969. */
  8970. /**
  8971. * The threshold option. Setting it to 0 will make the default
  8972. * navigator area series draw its area from the 0 value and up.
  8973. *
  8974. * @type {number|null}
  8975. */
  8976. threshold: null
  8977. },
  8978. /**
  8979. * Options for the navigator X axis. Default series options for the
  8980. * navigator xAxis are:
  8981. * ```js
  8982. * xAxis: {
  8983. * tickWidth: 0,
  8984. * lineWidth: 0,
  8985. * gridLineWidth: 1,
  8986. * tickPixelInterval: 200,
  8987. * labels: {
  8988. * align: 'left',
  8989. * style: {
  8990. * color: '#888'
  8991. * },
  8992. * x: 3,
  8993. * y: -4
  8994. * }
  8995. * }
  8996. * ```
  8997. *
  8998. * @extends xAxis
  8999. * @excluding linkedTo, maxZoom, minRange, opposite, range, scrollbar,
  9000. * showEmpty, maxRange
  9001. */
  9002. xAxis: {
  9003. /**
  9004. * Additional range on the right side of the xAxis. Works similar to
  9005. * xAxis.maxPadding, but value is set in milliseconds.
  9006. * Can be set for both, main xAxis and navigator's xAxis.
  9007. *
  9008. * @since 6.0.0
  9009. */
  9010. overscroll: 0,
  9011. className: 'highcharts-navigator-xaxis',
  9012. tickLength: 0,
  9013. lineWidth: 0,
  9014. gridLineColor: '#e6e6e6',
  9015. gridLineWidth: 1,
  9016. tickPixelInterval: 200,
  9017. labels: {
  9018. align: 'left',
  9019. /**
  9020. * @type {Highcharts.CSSObject}
  9021. */
  9022. style: {
  9023. /** @ignore */
  9024. color: '#999999'
  9025. },
  9026. x: 3,
  9027. y: -4
  9028. },
  9029. crosshair: false
  9030. },
  9031. /**
  9032. * Options for the navigator Y axis. Default series options for the
  9033. * navigator yAxis are:
  9034. * ```js
  9035. * yAxis: {
  9036. * gridLineWidth: 0,
  9037. * startOnTick: false,
  9038. * endOnTick: false,
  9039. * minPadding: 0.1,
  9040. * maxPadding: 0.1,
  9041. * labels: {
  9042. * enabled: false
  9043. * },
  9044. * title: {
  9045. * text: null
  9046. * },
  9047. * tickWidth: 0
  9048. * }
  9049. * ```
  9050. *
  9051. * @extends yAxis
  9052. * @excluding height, linkedTo, maxZoom, minRange, ordinal, range,
  9053. * showEmpty, scrollbar, top, units, maxRange, minLength,
  9054. * maxLength, resize
  9055. */
  9056. yAxis: {
  9057. className: 'highcharts-navigator-yaxis',
  9058. gridLineWidth: 0,
  9059. startOnTick: false,
  9060. endOnTick: false,
  9061. minPadding: 0.1,
  9062. maxPadding: 0.1,
  9063. labels: {
  9064. enabled: false
  9065. },
  9066. crosshair: false,
  9067. title: {
  9068. text: null
  9069. },
  9070. tickLength: 0,
  9071. tickWidth: 0
  9072. }
  9073. }
  9074. });
  9075. /* eslint-disable no-invalid-this, valid-jsdoc */
  9076. /**
  9077. * Draw one of the handles on the side of the zoomed range in the navigator
  9078. *
  9079. * @private
  9080. * @function Highcharts.Renderer#symbols.navigator-handle
  9081. * @param {number} x
  9082. * @param {number} y
  9083. * @param {number} w
  9084. * @param {number} h
  9085. * @param {Highcharts.NavigatorHandlesOptions} options
  9086. * @return {Highcharts.SVGPathArray}
  9087. * Path to be used in a handle
  9088. */
  9089. H.Renderer.prototype.symbols['navigator-handle'] = function (x, y, w, h, options) {
  9090. var halfWidth = (options && options.width || 0) / 2, markerPosition = Math.round(halfWidth / 3) + 0.5, height = options && options.height || 0;
  9091. return [
  9092. ['M', -halfWidth - 1, 0.5],
  9093. ['L', halfWidth, 0.5],
  9094. ['L', halfWidth, height + 0.5],
  9095. ['L', -halfWidth - 1, height + 0.5],
  9096. ['L', -halfWidth - 1, 0.5],
  9097. ['M', -markerPosition, 4],
  9098. ['L', -markerPosition, height - 3],
  9099. ['M', markerPosition - 1, 4],
  9100. ['L', markerPosition - 1, height - 3]
  9101. ];
  9102. };
  9103. /**
  9104. * The Navigator class
  9105. *
  9106. * @private
  9107. * @class
  9108. * @name Highcharts.Navigator
  9109. *
  9110. * @param {Highcharts.Chart} chart
  9111. * Chart object
  9112. */
  9113. var Navigator = /** @class */ (function () {
  9114. function Navigator(chart) {
  9115. this.baseSeries = void 0;
  9116. this.chart = void 0;
  9117. this.handles = void 0;
  9118. this.height = void 0;
  9119. this.left = void 0;
  9120. this.navigatorEnabled = void 0;
  9121. this.navigatorGroup = void 0;
  9122. this.navigatorOptions = void 0;
  9123. this.navigatorSeries = void 0;
  9124. this.navigatorSize = void 0;
  9125. this.opposite = void 0;
  9126. this.outline = void 0;
  9127. this.outlineHeight = void 0;
  9128. this.range = void 0;
  9129. this.rendered = void 0;
  9130. this.shades = void 0;
  9131. this.size = void 0;
  9132. this.top = void 0;
  9133. this.xAxis = void 0;
  9134. this.yAxis = void 0;
  9135. this.zoomedMax = void 0;
  9136. this.zoomedMin = void 0;
  9137. this.init(chart);
  9138. }
  9139. /**
  9140. * Draw one of the handles on the side of the zoomed range in the navigator
  9141. *
  9142. * @private
  9143. * @function Highcharts.Navigator#drawHandle
  9144. *
  9145. * @param {number} x
  9146. * The x center for the handle
  9147. *
  9148. * @param {number} index
  9149. * 0 for left and 1 for right
  9150. *
  9151. * @param {boolean|undefined} inverted
  9152. * flag for chart.inverted
  9153. *
  9154. * @param {string} verb
  9155. * use 'animate' or 'attr'
  9156. */
  9157. Navigator.prototype.drawHandle = function (x, index, inverted, verb) {
  9158. var navigator = this, height = navigator.navigatorOptions.handles.height;
  9159. // Place it
  9160. navigator.handles[index][verb](inverted ? {
  9161. translateX: Math.round(navigator.left + navigator.height / 2),
  9162. translateY: Math.round(navigator.top + parseInt(x, 10) + 0.5 - height)
  9163. } : {
  9164. translateX: Math.round(navigator.left + parseInt(x, 10)),
  9165. translateY: Math.round(navigator.top + navigator.height / 2 - height / 2 - 1)
  9166. });
  9167. };
  9168. /**
  9169. * Render outline around the zoomed range
  9170. *
  9171. * @private
  9172. * @function Highcharts.Navigator#drawOutline
  9173. *
  9174. * @param {number} zoomedMin
  9175. * in pixels position where zoomed range starts
  9176. *
  9177. * @param {number} zoomedMax
  9178. * in pixels position where zoomed range ends
  9179. *
  9180. * @param {boolean|undefined} inverted
  9181. * flag if chart is inverted
  9182. *
  9183. * @param {string} verb
  9184. * use 'animate' or 'attr'
  9185. */
  9186. Navigator.prototype.drawOutline = function (zoomedMin, zoomedMax, inverted, verb) {
  9187. var navigator = this, maskInside = navigator.navigatorOptions.maskInside, outlineWidth = navigator.outline.strokeWidth(), halfOutline = outlineWidth / 2, outlineCorrection = (outlineWidth % 2) / 2, // #5800
  9188. outlineHeight = navigator.outlineHeight, scrollbarHeight = navigator.scrollbarHeight || 0, navigatorSize = navigator.size, left = navigator.left - scrollbarHeight, navigatorTop = navigator.top, verticalMin, path;
  9189. if (inverted) {
  9190. left -= halfOutline;
  9191. verticalMin = navigatorTop + zoomedMax + outlineCorrection;
  9192. zoomedMax = navigatorTop + zoomedMin + outlineCorrection;
  9193. path = [
  9194. ['M', left + outlineHeight, navigatorTop - scrollbarHeight - outlineCorrection],
  9195. ['L', left + outlineHeight, verticalMin],
  9196. ['L', left, verticalMin],
  9197. ['L', left, zoomedMax],
  9198. ['L', left + outlineHeight, zoomedMax],
  9199. ['L', left + outlineHeight, navigatorTop + navigatorSize + scrollbarHeight]
  9200. ];
  9201. if (maskInside) {
  9202. path.push(['M', left + outlineHeight, verticalMin - halfOutline], // upper left of zoomed range
  9203. ['L', left + outlineHeight, zoomedMax + halfOutline] // upper right of z.r.
  9204. );
  9205. }
  9206. }
  9207. else {
  9208. zoomedMin += left + scrollbarHeight - outlineCorrection;
  9209. zoomedMax += left + scrollbarHeight - outlineCorrection;
  9210. navigatorTop += halfOutline;
  9211. path = [
  9212. ['M', left, navigatorTop],
  9213. ['L', zoomedMin, navigatorTop],
  9214. ['L', zoomedMin, navigatorTop + outlineHeight],
  9215. ['L', zoomedMax, navigatorTop + outlineHeight],
  9216. ['L', zoomedMax, navigatorTop],
  9217. ['L', left + navigatorSize + scrollbarHeight * 2, navigatorTop] // right
  9218. ];
  9219. if (maskInside) {
  9220. path.push(['M', zoomedMin - halfOutline, navigatorTop], // upper left of zoomed range
  9221. ['L', zoomedMax + halfOutline, navigatorTop] // upper right of z.r.
  9222. );
  9223. }
  9224. }
  9225. navigator.outline[verb]({
  9226. d: path
  9227. });
  9228. };
  9229. /**
  9230. * Render outline around the zoomed range
  9231. *
  9232. * @private
  9233. * @function Highcharts.Navigator#drawMasks
  9234. *
  9235. * @param {number} zoomedMin
  9236. * in pixels position where zoomed range starts
  9237. *
  9238. * @param {number} zoomedMax
  9239. * in pixels position where zoomed range ends
  9240. *
  9241. * @param {boolean|undefined} inverted
  9242. * flag if chart is inverted
  9243. *
  9244. * @param {string} verb
  9245. * use 'animate' or 'attr'
  9246. */
  9247. Navigator.prototype.drawMasks = function (zoomedMin, zoomedMax, inverted, verb) {
  9248. var navigator = this, left = navigator.left, top = navigator.top, navigatorHeight = navigator.height, height, width, x, y;
  9249. // Determine rectangle position & size
  9250. // According to (non)inverted position:
  9251. if (inverted) {
  9252. x = [left, left, left];
  9253. y = [top, top + zoomedMin, top + zoomedMax];
  9254. width = [navigatorHeight, navigatorHeight, navigatorHeight];
  9255. height = [
  9256. zoomedMin,
  9257. zoomedMax - zoomedMin,
  9258. navigator.size - zoomedMax
  9259. ];
  9260. }
  9261. else {
  9262. x = [left, left + zoomedMin, left + zoomedMax];
  9263. y = [top, top, top];
  9264. width = [
  9265. zoomedMin,
  9266. zoomedMax - zoomedMin,
  9267. navigator.size - zoomedMax
  9268. ];
  9269. height = [navigatorHeight, navigatorHeight, navigatorHeight];
  9270. }
  9271. navigator.shades.forEach(function (shade, i) {
  9272. shade[verb]({
  9273. x: x[i],
  9274. y: y[i],
  9275. width: width[i],
  9276. height: height[i]
  9277. });
  9278. });
  9279. };
  9280. /**
  9281. * Generate DOM elements for a navigator:
  9282. *
  9283. * - main navigator group
  9284. *
  9285. * - all shades
  9286. *
  9287. * - outline
  9288. *
  9289. * - handles
  9290. *
  9291. * @private
  9292. * @function Highcharts.Navigator#renderElements
  9293. */
  9294. Navigator.prototype.renderElements = function () {
  9295. var navigator = this, navigatorOptions = navigator.navigatorOptions, maskInside = navigatorOptions.maskInside, chart = navigator.chart, inverted = chart.inverted, renderer = chart.renderer, navigatorGroup, mouseCursor = {
  9296. cursor: inverted ? 'ns-resize' : 'ew-resize'
  9297. };
  9298. // Create the main navigator group
  9299. navigator.navigatorGroup = navigatorGroup = renderer.g('navigator')
  9300. .attr({
  9301. zIndex: 8,
  9302. visibility: 'hidden'
  9303. })
  9304. .add();
  9305. // Create masks, each mask will get events and fill:
  9306. [
  9307. !maskInside,
  9308. maskInside,
  9309. !maskInside
  9310. ].forEach(function (hasMask, index) {
  9311. navigator.shades[index] = renderer.rect()
  9312. .addClass('highcharts-navigator-mask' +
  9313. (index === 1 ? '-inside' : '-outside'))
  9314. .add(navigatorGroup);
  9315. if (!chart.styledMode) {
  9316. navigator.shades[index]
  9317. .attr({
  9318. fill: hasMask ?
  9319. navigatorOptions.maskFill :
  9320. 'rgba(0,0,0,0)'
  9321. })
  9322. .css((index === 1) && mouseCursor);
  9323. }
  9324. });
  9325. // Create the outline:
  9326. navigator.outline = renderer.path()
  9327. .addClass('highcharts-navigator-outline')
  9328. .add(navigatorGroup);
  9329. if (!chart.styledMode) {
  9330. navigator.outline.attr({
  9331. 'stroke-width': navigatorOptions.outlineWidth,
  9332. stroke: navigatorOptions.outlineColor
  9333. });
  9334. }
  9335. // Create the handlers:
  9336. if (navigatorOptions.handles.enabled) {
  9337. [0, 1].forEach(function (index) {
  9338. navigatorOptions.handles.inverted = chart.inverted;
  9339. navigator.handles[index] = renderer.symbol(navigatorOptions.handles.symbols[index], -navigatorOptions.handles.width / 2 - 1, 0, navigatorOptions.handles.width, navigatorOptions.handles.height, navigatorOptions.handles);
  9340. // zIndex = 6 for right handle, 7 for left.
  9341. // Can't be 10, because of the tooltip in inverted chart #2908
  9342. navigator.handles[index].attr({ zIndex: 7 - index })
  9343. .addClass('highcharts-navigator-handle ' +
  9344. 'highcharts-navigator-handle-' +
  9345. ['left', 'right'][index]).add(navigatorGroup);
  9346. if (!chart.styledMode) {
  9347. var handlesOptions = navigatorOptions.handles;
  9348. navigator.handles[index]
  9349. .attr({
  9350. fill: handlesOptions.backgroundColor,
  9351. stroke: handlesOptions.borderColor,
  9352. 'stroke-width': handlesOptions.lineWidth
  9353. })
  9354. .css(mouseCursor);
  9355. }
  9356. });
  9357. }
  9358. };
  9359. /**
  9360. * Update navigator
  9361. *
  9362. * @private
  9363. * @function Highcharts.Navigator#update
  9364. *
  9365. * @param {Highcharts.NavigatorOptions} options
  9366. * Options to merge in when updating navigator
  9367. */
  9368. Navigator.prototype.update = function (options) {
  9369. // Remove references to old navigator series in base series
  9370. (this.series || []).forEach(function (series) {
  9371. if (series.baseSeries) {
  9372. delete series.baseSeries.navigatorSeries;
  9373. }
  9374. });
  9375. // Destroy and rebuild navigator
  9376. this.destroy();
  9377. var chartOptions = this.chart.options;
  9378. merge(true, chartOptions.navigator, this.options, options);
  9379. this.init(this.chart);
  9380. };
  9381. /**
  9382. * Render the navigator
  9383. *
  9384. * @private
  9385. * @function Highcharts.Navigator#render
  9386. * @param {number} min
  9387. * X axis value minimum
  9388. * @param {number} max
  9389. * X axis value maximum
  9390. * @param {number} [pxMin]
  9391. * Pixel value minimum
  9392. * @param {number} [pxMax]
  9393. * Pixel value maximum
  9394. * @return {void}
  9395. */
  9396. Navigator.prototype.render = function (min, max, pxMin, pxMax) {
  9397. var navigator = this, chart = navigator.chart, navigatorWidth, scrollbarLeft, scrollbarTop, scrollbarHeight = navigator.scrollbarHeight, navigatorSize, xAxis = navigator.xAxis, pointRange = xAxis.pointRange || 0, scrollbarXAxis = xAxis.navigatorAxis.fake ? chart.xAxis[0] : xAxis, navigatorEnabled = navigator.navigatorEnabled, zoomedMin, zoomedMax, rendered = navigator.rendered, inverted = chart.inverted, verb, newMin, newMax, currentRange, minRange = chart.xAxis[0].minRange, maxRange = chart.xAxis[0].options.maxRange;
  9398. // Don't redraw while moving the handles (#4703).
  9399. if (this.hasDragged && !defined(pxMin)) {
  9400. return;
  9401. }
  9402. min = correctFloat(min - pointRange / 2);
  9403. max = correctFloat(max + pointRange / 2);
  9404. // Don't render the navigator until we have data (#486, #4202, #5172).
  9405. if (!isNumber(min) || !isNumber(max)) {
  9406. // However, if navigator was already rendered, we may need to resize
  9407. // it. For example hidden series, but visible navigator (#6022).
  9408. if (rendered) {
  9409. pxMin = 0;
  9410. pxMax = pick(xAxis.width, scrollbarXAxis.width);
  9411. }
  9412. else {
  9413. return;
  9414. }
  9415. }
  9416. navigator.left = pick(xAxis.left,
  9417. // in case of scrollbar only, without navigator
  9418. chart.plotLeft + scrollbarHeight +
  9419. (inverted ? chart.plotWidth : 0));
  9420. navigator.size = zoomedMax = navigatorSize = pick(xAxis.len, (inverted ? chart.plotHeight : chart.plotWidth) -
  9421. 2 * scrollbarHeight);
  9422. if (inverted) {
  9423. navigatorWidth = scrollbarHeight;
  9424. }
  9425. else {
  9426. navigatorWidth = navigatorSize + 2 * scrollbarHeight;
  9427. }
  9428. // Get the pixel position of the handles
  9429. pxMin = pick(pxMin, xAxis.toPixels(min, true));
  9430. pxMax = pick(pxMax, xAxis.toPixels(max, true));
  9431. // Verify (#1851, #2238)
  9432. if (!isNumber(pxMin) || Math.abs(pxMin) === Infinity) {
  9433. pxMin = 0;
  9434. pxMax = navigatorWidth;
  9435. }
  9436. // Are we below the minRange? (#2618, #6191)
  9437. newMin = xAxis.toValue(pxMin, true);
  9438. newMax = xAxis.toValue(pxMax, true);
  9439. currentRange = Math.abs(correctFloat(newMax - newMin));
  9440. if (currentRange < minRange) {
  9441. if (this.grabbedLeft) {
  9442. pxMin = xAxis.toPixels(newMax - minRange - pointRange, true);
  9443. }
  9444. else if (this.grabbedRight) {
  9445. pxMax = xAxis.toPixels(newMin + minRange + pointRange, true);
  9446. }
  9447. }
  9448. else if (defined(maxRange) &&
  9449. correctFloat(currentRange - pointRange) > maxRange) {
  9450. if (this.grabbedLeft) {
  9451. pxMin = xAxis.toPixels(newMax - maxRange - pointRange, true);
  9452. }
  9453. else if (this.grabbedRight) {
  9454. pxMax = xAxis.toPixels(newMin + maxRange + pointRange, true);
  9455. }
  9456. }
  9457. // Handles are allowed to cross, but never exceed the plot area
  9458. navigator.zoomedMax = clamp(Math.max(pxMin, pxMax), 0, zoomedMax);
  9459. navigator.zoomedMin = clamp(navigator.fixedWidth ?
  9460. navigator.zoomedMax - navigator.fixedWidth :
  9461. Math.min(pxMin, pxMax), 0, zoomedMax);
  9462. navigator.range = navigator.zoomedMax - navigator.zoomedMin;
  9463. zoomedMax = Math.round(navigator.zoomedMax);
  9464. zoomedMin = Math.round(navigator.zoomedMin);
  9465. if (navigatorEnabled) {
  9466. navigator.navigatorGroup.attr({
  9467. visibility: 'visible'
  9468. });
  9469. // Place elements
  9470. verb = rendered && !navigator.hasDragged ? 'animate' : 'attr';
  9471. navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
  9472. navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
  9473. if (navigator.navigatorOptions.handles.enabled) {
  9474. navigator.drawHandle(zoomedMin, 0, inverted, verb);
  9475. navigator.drawHandle(zoomedMax, 1, inverted, verb);
  9476. }
  9477. }
  9478. if (navigator.scrollbar) {
  9479. if (inverted) {
  9480. scrollbarTop = navigator.top - scrollbarHeight;
  9481. scrollbarLeft = navigator.left - scrollbarHeight +
  9482. (navigatorEnabled || !scrollbarXAxis.opposite ? 0 :
  9483. // Multiple axes has offsets:
  9484. (scrollbarXAxis.titleOffset || 0) +
  9485. // Self margin from the axis.title
  9486. scrollbarXAxis.axisTitleMargin);
  9487. scrollbarHeight = navigatorSize + 2 * scrollbarHeight;
  9488. }
  9489. else {
  9490. scrollbarTop = navigator.top + (navigatorEnabled ?
  9491. navigator.height :
  9492. -scrollbarHeight);
  9493. scrollbarLeft = navigator.left - scrollbarHeight;
  9494. }
  9495. // Reposition scrollbar
  9496. navigator.scrollbar.position(scrollbarLeft, scrollbarTop, navigatorWidth, scrollbarHeight);
  9497. // Keep scale 0-1
  9498. navigator.scrollbar.setRange(
  9499. // Use real value, not rounded because range can be very small
  9500. // (#1716)
  9501. navigator.zoomedMin / (navigatorSize || 1), navigator.zoomedMax / (navigatorSize || 1));
  9502. }
  9503. navigator.rendered = true;
  9504. };
  9505. /**
  9506. * Set up the mouse and touch events for the navigator
  9507. *
  9508. * @private
  9509. * @function Highcharts.Navigator#addMouseEvents
  9510. */
  9511. Navigator.prototype.addMouseEvents = function () {
  9512. var navigator = this, chart = navigator.chart, container = chart.container, eventsToUnbind = [], mouseMoveHandler, mouseUpHandler;
  9513. /**
  9514. * Create mouse events' handlers.
  9515. * Make them as separate functions to enable wrapping them:
  9516. */
  9517. navigator.mouseMoveHandler = mouseMoveHandler = function (e) {
  9518. navigator.onMouseMove(e);
  9519. };
  9520. navigator.mouseUpHandler = mouseUpHandler = function (e) {
  9521. navigator.onMouseUp(e);
  9522. };
  9523. // Add shades and handles mousedown events
  9524. eventsToUnbind = navigator.getPartsEvents('mousedown');
  9525. // Add mouse move and mouseup events. These are bind to doc/container,
  9526. // because Navigator.grabbedSomething flags are stored in mousedown
  9527. // events
  9528. eventsToUnbind.push(addEvent(chart.renderTo, 'mousemove', mouseMoveHandler), addEvent(container.ownerDocument, 'mouseup', mouseUpHandler));
  9529. // Touch events
  9530. if (hasTouch) {
  9531. eventsToUnbind.push(addEvent(chart.renderTo, 'touchmove', mouseMoveHandler), addEvent(container.ownerDocument, 'touchend', mouseUpHandler));
  9532. eventsToUnbind.concat(navigator.getPartsEvents('touchstart'));
  9533. }
  9534. navigator.eventsToUnbind = eventsToUnbind;
  9535. // Data events
  9536. if (navigator.series && navigator.series[0]) {
  9537. eventsToUnbind.push(addEvent(navigator.series[0].xAxis, 'foundExtremes', function () {
  9538. chart.navigator.modifyNavigatorAxisExtremes();
  9539. }));
  9540. }
  9541. };
  9542. /**
  9543. * Generate events for handles and masks
  9544. *
  9545. * @private
  9546. * @function Highcharts.Navigator#getPartsEvents
  9547. *
  9548. * @param {string} eventName
  9549. * Event name handler, 'mousedown' or 'touchstart'
  9550. *
  9551. * @return {Array<Function>}
  9552. * An array of functions to remove navigator functions from the
  9553. * events again.
  9554. */
  9555. Navigator.prototype.getPartsEvents = function (eventName) {
  9556. var navigator = this, events = [];
  9557. ['shades', 'handles'].forEach(function (name) {
  9558. navigator[name].forEach(function (navigatorItem, index) {
  9559. events.push(addEvent(navigatorItem.element, eventName, function (e) {
  9560. navigator[name + 'Mousedown'](e, index);
  9561. }));
  9562. });
  9563. });
  9564. return events;
  9565. };
  9566. /**
  9567. * Mousedown on a shaded mask, either:
  9568. *
  9569. * - will be stored for future drag&drop
  9570. *
  9571. * - will directly shift to a new range
  9572. *
  9573. * @private
  9574. * @function Highcharts.Navigator#shadesMousedown
  9575. *
  9576. * @param {Highcharts.PointerEventObject} e
  9577. * Mouse event
  9578. *
  9579. * @param {number} index
  9580. * Index of a mask in Navigator.shades array
  9581. */
  9582. Navigator.prototype.shadesMousedown = function (e, index) {
  9583. e = this.chart.pointer.normalize(e);
  9584. var navigator = this, chart = navigator.chart, xAxis = navigator.xAxis, zoomedMin = navigator.zoomedMin, navigatorPosition = navigator.left, navigatorSize = navigator.size, range = navigator.range, chartX = e.chartX, fixedMax, fixedMin, ext, left;
  9585. // For inverted chart, swap some options:
  9586. if (chart.inverted) {
  9587. chartX = e.chartY;
  9588. navigatorPosition = navigator.top;
  9589. }
  9590. if (index === 1) {
  9591. // Store information for drag&drop
  9592. navigator.grabbedCenter = chartX;
  9593. navigator.fixedWidth = range;
  9594. navigator.dragOffset = chartX - zoomedMin;
  9595. }
  9596. else {
  9597. // Shift the range by clicking on shaded areas
  9598. left = chartX - navigatorPosition - range / 2;
  9599. if (index === 0) {
  9600. left = Math.max(0, left);
  9601. }
  9602. else if (index === 2 && left + range >= navigatorSize) {
  9603. left = navigatorSize - range;
  9604. if (navigator.reversedExtremes) {
  9605. // #7713
  9606. left -= range;
  9607. fixedMin = navigator.getUnionExtremes().dataMin;
  9608. }
  9609. else {
  9610. // #2293, #3543
  9611. fixedMax = navigator.getUnionExtremes().dataMax;
  9612. }
  9613. }
  9614. if (left !== zoomedMin) { // it has actually moved
  9615. navigator.fixedWidth = range; // #1370
  9616. ext = xAxis.navigatorAxis.toFixedRange(left, left + range, fixedMin, fixedMax);
  9617. if (defined(ext.min)) { // #7411
  9618. chart.xAxis[0].setExtremes(Math.min(ext.min, ext.max), Math.max(ext.min, ext.max), true, null, // auto animation
  9619. { trigger: 'navigator' });
  9620. }
  9621. }
  9622. }
  9623. };
  9624. /**
  9625. * Mousedown on a handle mask.
  9626. * Will store necessary information for drag&drop.
  9627. *
  9628. * @private
  9629. * @function Highcharts.Navigator#handlesMousedown
  9630. * @param {Highcharts.PointerEventObject} e
  9631. * Mouse event
  9632. * @param {number} index
  9633. * Index of a handle in Navigator.handles array
  9634. * @return {void}
  9635. */
  9636. Navigator.prototype.handlesMousedown = function (e, index) {
  9637. e = this.chart.pointer.normalize(e);
  9638. var navigator = this, chart = navigator.chart, baseXAxis = chart.xAxis[0],
  9639. // For reversed axes, min and max are changed,
  9640. // so the other extreme should be stored
  9641. reverse = navigator.reversedExtremes;
  9642. if (index === 0) {
  9643. // Grab the left handle
  9644. navigator.grabbedLeft = true;
  9645. navigator.otherHandlePos = navigator.zoomedMax;
  9646. navigator.fixedExtreme = reverse ? baseXAxis.min : baseXAxis.max;
  9647. }
  9648. else {
  9649. // Grab the right handle
  9650. navigator.grabbedRight = true;
  9651. navigator.otherHandlePos = navigator.zoomedMin;
  9652. navigator.fixedExtreme = reverse ? baseXAxis.max : baseXAxis.min;
  9653. }
  9654. chart.fixedRange = null;
  9655. };
  9656. /**
  9657. * Mouse move event based on x/y mouse position.
  9658. *
  9659. * @private
  9660. * @function Highcharts.Navigator#onMouseMove
  9661. *
  9662. * @param {Highcharts.PointerEventObject} e
  9663. * Mouse event
  9664. */
  9665. Navigator.prototype.onMouseMove = function (e) {
  9666. var navigator = this, chart = navigator.chart, left = navigator.left, navigatorSize = navigator.navigatorSize, range = navigator.range, dragOffset = navigator.dragOffset, inverted = chart.inverted, chartX;
  9667. // In iOS, a mousemove event with e.pageX === 0 is fired when holding
  9668. // the finger down in the center of the scrollbar. This should be
  9669. // ignored.
  9670. if (!e.touches || e.touches[0].pageX !== 0) { // #4696
  9671. e = chart.pointer.normalize(e);
  9672. chartX = e.chartX;
  9673. // Swap some options for inverted chart
  9674. if (inverted) {
  9675. left = navigator.top;
  9676. chartX = e.chartY;
  9677. }
  9678. // Drag left handle or top handle
  9679. if (navigator.grabbedLeft) {
  9680. navigator.hasDragged = true;
  9681. navigator.render(0, 0, chartX - left, navigator.otherHandlePos);
  9682. // Drag right handle or bottom handle
  9683. }
  9684. else if (navigator.grabbedRight) {
  9685. navigator.hasDragged = true;
  9686. navigator.render(0, 0, navigator.otherHandlePos, chartX - left);
  9687. // Drag scrollbar or open area in navigator
  9688. }
  9689. else if (navigator.grabbedCenter) {
  9690. navigator.hasDragged = true;
  9691. if (chartX < dragOffset) { // outside left
  9692. chartX = dragOffset;
  9693. // outside right
  9694. }
  9695. else if (chartX >
  9696. navigatorSize + dragOffset - range) {
  9697. chartX = navigatorSize + dragOffset - range;
  9698. }
  9699. navigator.render(0, 0, chartX - dragOffset, chartX - dragOffset + range);
  9700. }
  9701. if (navigator.hasDragged &&
  9702. navigator.scrollbar &&
  9703. pick(navigator.scrollbar.options.liveRedraw,
  9704. // By default, don't run live redraw on VML, on touch
  9705. // devices or if the chart is in boost.
  9706. H.svg && !isTouchDevice && !this.chart.isBoosting)) {
  9707. e.DOMType = e.type; // DOMType is for IE8
  9708. setTimeout(function () {
  9709. navigator.onMouseUp(e);
  9710. }, 0);
  9711. }
  9712. }
  9713. };
  9714. /**
  9715. * Mouse up event based on x/y mouse position.
  9716. *
  9717. * @private
  9718. * @function Highcharts.Navigator#onMouseUp
  9719. * @param {Highcharts.PointerEventObject} e
  9720. * Mouse event
  9721. * @return {void}
  9722. */
  9723. Navigator.prototype.onMouseUp = function (e) {
  9724. var navigator = this, chart = navigator.chart, xAxis = navigator.xAxis, scrollbar = navigator.scrollbar, DOMEvent = e.DOMEvent || e, inverted = chart.inverted, verb = navigator.rendered && !navigator.hasDragged ?
  9725. 'animate' : 'attr', zoomedMax = Math.round(navigator.zoomedMax), zoomedMin = Math.round(navigator.zoomedMin), unionExtremes, fixedMin, fixedMax, ext;
  9726. if (
  9727. // MouseUp is called for both, navigator and scrollbar (that order),
  9728. // which causes calling afterSetExtremes twice. Prevent first call
  9729. // by checking if scrollbar is going to set new extremes (#6334)
  9730. (navigator.hasDragged && (!scrollbar || !scrollbar.hasDragged)) ||
  9731. e.trigger === 'scrollbar') {
  9732. unionExtremes = navigator.getUnionExtremes();
  9733. // When dragging one handle, make sure the other one doesn't change
  9734. if (navigator.zoomedMin === navigator.otherHandlePos) {
  9735. fixedMin = navigator.fixedExtreme;
  9736. }
  9737. else if (navigator.zoomedMax === navigator.otherHandlePos) {
  9738. fixedMax = navigator.fixedExtreme;
  9739. }
  9740. // Snap to right edge (#4076)
  9741. if (navigator.zoomedMax === navigator.size) {
  9742. fixedMax = navigator.reversedExtremes ?
  9743. unionExtremes.dataMin :
  9744. unionExtremes.dataMax;
  9745. }
  9746. // Snap to left edge (#7576)
  9747. if (navigator.zoomedMin === 0) {
  9748. fixedMin = navigator.reversedExtremes ?
  9749. unionExtremes.dataMax :
  9750. unionExtremes.dataMin;
  9751. }
  9752. ext = xAxis.navigatorAxis.toFixedRange(navigator.zoomedMin, navigator.zoomedMax, fixedMin, fixedMax);
  9753. if (defined(ext.min)) {
  9754. chart.xAxis[0].setExtremes(Math.min(ext.min, ext.max), Math.max(ext.min, ext.max), true,
  9755. // Run animation when clicking buttons, scrollbar track etc,
  9756. // but not when dragging handles or scrollbar
  9757. navigator.hasDragged ? false : null, {
  9758. trigger: 'navigator',
  9759. triggerOp: 'navigator-drag',
  9760. DOMEvent: DOMEvent // #1838
  9761. });
  9762. }
  9763. }
  9764. if (e.DOMType !== 'mousemove' &&
  9765. e.DOMType !== 'touchmove') {
  9766. navigator.grabbedLeft = navigator.grabbedRight =
  9767. navigator.grabbedCenter = navigator.fixedWidth =
  9768. navigator.fixedExtreme = navigator.otherHandlePos =
  9769. navigator.hasDragged = navigator.dragOffset = null;
  9770. }
  9771. // Update position of navigator shades, outline and handles (#12573)
  9772. if (navigator.navigatorEnabled) {
  9773. if (navigator.shades) {
  9774. navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
  9775. }
  9776. if (navigator.outline) {
  9777. navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
  9778. }
  9779. if (navigator.navigatorOptions.handles.enabled &&
  9780. Object.keys(navigator.handles).length ===
  9781. navigator.handles.length) {
  9782. navigator.drawHandle(zoomedMin, 0, inverted, verb);
  9783. navigator.drawHandle(zoomedMax, 1, inverted, verb);
  9784. }
  9785. }
  9786. };
  9787. /**
  9788. * Removes the event handlers attached previously with addEvents.
  9789. *
  9790. * @private
  9791. * @function Highcharts.Navigator#removeEvents
  9792. * @return {void}
  9793. */
  9794. Navigator.prototype.removeEvents = function () {
  9795. if (this.eventsToUnbind) {
  9796. this.eventsToUnbind.forEach(function (unbind) {
  9797. unbind();
  9798. });
  9799. this.eventsToUnbind = void 0;
  9800. }
  9801. this.removeBaseSeriesEvents();
  9802. };
  9803. /**
  9804. * Remove data events.
  9805. *
  9806. * @private
  9807. * @function Highcharts.Navigator#removeBaseSeriesEvents
  9808. * @return {void}
  9809. */
  9810. Navigator.prototype.removeBaseSeriesEvents = function () {
  9811. var baseSeries = this.baseSeries || [];
  9812. if (this.navigatorEnabled && baseSeries[0]) {
  9813. if (this.navigatorOptions.adaptToUpdatedData !== false) {
  9814. baseSeries.forEach(function (series) {
  9815. removeEvent(series, 'updatedData', this.updatedDataHandler);
  9816. }, this);
  9817. }
  9818. // We only listen for extremes-events on the first baseSeries
  9819. if (baseSeries[0].xAxis) {
  9820. removeEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes);
  9821. }
  9822. }
  9823. };
  9824. /**
  9825. * Initialize the Navigator object
  9826. *
  9827. * @private
  9828. * @function Highcharts.Navigator#init
  9829. *
  9830. * @param {Highcharts.Chart} chart
  9831. */
  9832. Navigator.prototype.init = function (chart) {
  9833. var chartOptions = chart.options, navigatorOptions = chartOptions.navigator, navigatorEnabled = navigatorOptions.enabled, scrollbarOptions = chartOptions.scrollbar, scrollbarEnabled = scrollbarOptions.enabled, height = navigatorEnabled ? navigatorOptions.height : 0, scrollbarHeight = scrollbarEnabled ?
  9834. scrollbarOptions.height :
  9835. 0;
  9836. this.handles = [];
  9837. this.shades = [];
  9838. this.chart = chart;
  9839. this.setBaseSeries();
  9840. this.height = height;
  9841. this.scrollbarHeight = scrollbarHeight;
  9842. this.scrollbarEnabled = scrollbarEnabled;
  9843. this.navigatorEnabled = navigatorEnabled;
  9844. this.navigatorOptions = navigatorOptions;
  9845. this.scrollbarOptions = scrollbarOptions;
  9846. this.outlineHeight = height + scrollbarHeight;
  9847. this.opposite = pick(navigatorOptions.opposite, Boolean(!navigatorEnabled && chart.inverted)); // #6262
  9848. var navigator = this, baseSeries = navigator.baseSeries, xAxisIndex = chart.xAxis.length, yAxisIndex = chart.yAxis.length, baseXaxis = baseSeries && baseSeries[0] && baseSeries[0].xAxis ||
  9849. chart.xAxis[0] || { options: {} };
  9850. chart.isDirtyBox = true;
  9851. if (navigator.navigatorEnabled) {
  9852. // an x axis is required for scrollbar also
  9853. navigator.xAxis = new Axis(chart, merge({
  9854. // inherit base xAxis' break and ordinal options
  9855. breaks: baseXaxis.options.breaks,
  9856. ordinal: baseXaxis.options.ordinal
  9857. }, navigatorOptions.xAxis, {
  9858. id: 'navigator-x-axis',
  9859. yAxis: 'navigator-y-axis',
  9860. isX: true,
  9861. type: 'datetime',
  9862. index: xAxisIndex,
  9863. isInternal: true,
  9864. offset: 0,
  9865. keepOrdinalPadding: true,
  9866. startOnTick: false,
  9867. endOnTick: false,
  9868. minPadding: 0,
  9869. maxPadding: 0,
  9870. zoomEnabled: false
  9871. }, chart.inverted ? {
  9872. offsets: [scrollbarHeight, 0, -scrollbarHeight, 0],
  9873. width: height
  9874. } : {
  9875. offsets: [0, -scrollbarHeight, 0, scrollbarHeight],
  9876. height: height
  9877. }));
  9878. navigator.yAxis = new Axis(chart, merge(navigatorOptions.yAxis, {
  9879. id: 'navigator-y-axis',
  9880. alignTicks: false,
  9881. offset: 0,
  9882. index: yAxisIndex,
  9883. isInternal: true,
  9884. zoomEnabled: false
  9885. }, chart.inverted ? {
  9886. width: height
  9887. } : {
  9888. height: height
  9889. }));
  9890. // If we have a base series, initialize the navigator series
  9891. if (baseSeries || navigatorOptions.series.data) {
  9892. navigator.updateNavigatorSeries(false);
  9893. // If not, set up an event to listen for added series
  9894. }
  9895. else if (chart.series.length === 0) {
  9896. navigator.unbindRedraw = addEvent(chart, 'beforeRedraw', function () {
  9897. // We've got one, now add it as base
  9898. if (chart.series.length > 0 && !navigator.series) {
  9899. navigator.setBaseSeries();
  9900. navigator.unbindRedraw(); // reset
  9901. }
  9902. });
  9903. }
  9904. navigator.reversedExtremes = (chart.inverted && !navigator.xAxis.reversed) || (!chart.inverted && navigator.xAxis.reversed);
  9905. // Render items, so we can bind events to them:
  9906. navigator.renderElements();
  9907. // Add mouse events
  9908. navigator.addMouseEvents();
  9909. // in case of scrollbar only, fake an x axis to get translation
  9910. }
  9911. else {
  9912. navigator.xAxis = {
  9913. chart: chart,
  9914. navigatorAxis: {
  9915. fake: true
  9916. },
  9917. translate: function (value, reverse) {
  9918. var axis = chart.xAxis[0], ext = axis.getExtremes(), scrollTrackWidth = axis.len - 2 * scrollbarHeight, min = numExt('min', axis.options.min, ext.dataMin), valueRange = numExt('max', axis.options.max, ext.dataMax) - min;
  9919. return reverse ?
  9920. // from pixel to value
  9921. (value * valueRange / scrollTrackWidth) + min :
  9922. // from value to pixel
  9923. scrollTrackWidth * (value - min) / valueRange;
  9924. },
  9925. toPixels: function (value) {
  9926. return this.translate(value);
  9927. },
  9928. toValue: function (value) {
  9929. return this.translate(value, true);
  9930. }
  9931. };
  9932. navigator.xAxis.navigatorAxis.axis = navigator.xAxis;
  9933. navigator.xAxis.navigatorAxis.toFixedRange = (NavigatorAxis.AdditionsClass.prototype.toFixedRange.bind(navigator.xAxis.navigatorAxis));
  9934. }
  9935. // Initialize the scrollbar
  9936. if (chart.options.scrollbar.enabled) {
  9937. chart.scrollbar = navigator.scrollbar = new Scrollbar(chart.renderer, merge(chart.options.scrollbar, {
  9938. margin: navigator.navigatorEnabled ? 0 : 10,
  9939. vertical: chart.inverted
  9940. }), chart);
  9941. addEvent(navigator.scrollbar, 'changed', function (e) {
  9942. var range = navigator.size, to = range * this.to, from = range * this.from;
  9943. navigator.hasDragged = navigator.scrollbar.hasDragged;
  9944. navigator.render(0, 0, from, to);
  9945. if (chart.options.scrollbar.liveRedraw ||
  9946. (e.DOMType !== 'mousemove' &&
  9947. e.DOMType !== 'touchmove')) {
  9948. setTimeout(function () {
  9949. navigator.onMouseUp(e);
  9950. });
  9951. }
  9952. });
  9953. }
  9954. // Add data events
  9955. navigator.addBaseSeriesEvents();
  9956. // Add redraw events
  9957. navigator.addChartEvents();
  9958. };
  9959. /**
  9960. * Get the union data extremes of the chart - the outer data extremes of the
  9961. * base X axis and the navigator axis.
  9962. *
  9963. * @private
  9964. * @function Highcharts.Navigator#getUnionExtremes
  9965. * @param {boolean} [returnFalseOnNoBaseSeries]
  9966. * as the param says.
  9967. * @return {Highcharts.Dictionary<(number|undefined)>|undefined}
  9968. */
  9969. Navigator.prototype.getUnionExtremes = function (returnFalseOnNoBaseSeries) {
  9970. var baseAxis = this.chart.xAxis[0], navAxis = this.xAxis, navAxisOptions = navAxis.options, baseAxisOptions = baseAxis.options, ret;
  9971. if (!returnFalseOnNoBaseSeries || baseAxis.dataMin !== null) {
  9972. ret = {
  9973. dataMin: pick(// #4053
  9974. navAxisOptions && navAxisOptions.min, numExt('min', baseAxisOptions.min, baseAxis.dataMin, navAxis.dataMin, navAxis.min)),
  9975. dataMax: pick(navAxisOptions && navAxisOptions.max, numExt('max', baseAxisOptions.max, baseAxis.dataMax, navAxis.dataMax, navAxis.max))
  9976. };
  9977. }
  9978. return ret;
  9979. };
  9980. /**
  9981. * Set the base series and update the navigator series from this. With a bit
  9982. * of modification we should be able to make this an API method to be called
  9983. * from the outside
  9984. *
  9985. * @private
  9986. * @function Highcharts.Navigator#setBaseSeries
  9987. * @param {Highcharts.SeriesOptionsType} [baseSeriesOptions]
  9988. * Additional series options for a navigator
  9989. * @param {boolean} [redraw]
  9990. * Whether to redraw after update.
  9991. * @return {void}
  9992. */
  9993. Navigator.prototype.setBaseSeries = function (baseSeriesOptions, redraw) {
  9994. var chart = this.chart, baseSeries = this.baseSeries = [];
  9995. baseSeriesOptions = (baseSeriesOptions ||
  9996. chart.options && chart.options.navigator.baseSeries ||
  9997. (chart.series.length ?
  9998. // Find the first non-navigator series (#8430)
  9999. find(chart.series, function (s) {
  10000. return !s.options.isInternal;
  10001. }).index :
  10002. 0));
  10003. // Iterate through series and add the ones that should be shown in
  10004. // navigator.
  10005. (chart.series || []).forEach(function (series, i) {
  10006. if (
  10007. // Don't include existing nav series
  10008. !series.options.isInternal &&
  10009. (series.options.showInNavigator ||
  10010. (i === baseSeriesOptions ||
  10011. series.options.id === baseSeriesOptions) &&
  10012. series.options.showInNavigator !== false)) {
  10013. baseSeries.push(series);
  10014. }
  10015. });
  10016. // When run after render, this.xAxis already exists
  10017. if (this.xAxis && !this.xAxis.navigatorAxis.fake) {
  10018. this.updateNavigatorSeries(true, redraw);
  10019. }
  10020. };
  10021. /**
  10022. * Update series in the navigator from baseSeries, adding new if does not
  10023. * exist.
  10024. *
  10025. * @private
  10026. * @function Highcharts.Navigator.updateNavigatorSeries
  10027. * @param {boolean} addEvents
  10028. * @param {boolean} [redraw]
  10029. * @return {void}
  10030. */
  10031. Navigator.prototype.updateNavigatorSeries = function (addEvents, redraw) {
  10032. var navigator = this, chart = navigator.chart, baseSeries = navigator.baseSeries, baseOptions, mergedNavSeriesOptions, chartNavigatorSeriesOptions = navigator.navigatorOptions.series, baseNavigatorOptions, navSeriesMixin = {
  10033. enableMouseTracking: false,
  10034. index: null,
  10035. linkedTo: null,
  10036. group: 'nav',
  10037. padXAxis: false,
  10038. xAxis: 'navigator-x-axis',
  10039. yAxis: 'navigator-y-axis',
  10040. showInLegend: false,
  10041. stacking: void 0,
  10042. isInternal: true,
  10043. states: {
  10044. inactive: {
  10045. opacity: 1
  10046. }
  10047. }
  10048. },
  10049. // Remove navigator series that are no longer in the baseSeries
  10050. navigatorSeries = navigator.series =
  10051. (navigator.series || []).filter(function (navSeries) {
  10052. var base = navSeries.baseSeries;
  10053. if (baseSeries.indexOf(base) < 0) { // Not in array
  10054. // If there is still a base series connected to this
  10055. // series, remove event handler and reference.
  10056. if (base) {
  10057. removeEvent(base, 'updatedData', navigator.updatedDataHandler);
  10058. delete base.navigatorSeries;
  10059. }
  10060. // Kill the nav series. It may already have been
  10061. // destroyed (#8715).
  10062. if (navSeries.chart) {
  10063. navSeries.destroy();
  10064. }
  10065. return false;
  10066. }
  10067. return true;
  10068. });
  10069. // Go through each base series and merge the options to create new
  10070. // series
  10071. if (baseSeries && baseSeries.length) {
  10072. baseSeries.forEach(function eachBaseSeries(base) {
  10073. var linkedNavSeries = base.navigatorSeries, userNavOptions = extend(
  10074. // Grab color and visibility from base as default
  10075. {
  10076. color: base.color,
  10077. visible: base.visible
  10078. }, !isArray(chartNavigatorSeriesOptions) ?
  10079. chartNavigatorSeriesOptions :
  10080. defaultOptions.navigator.series);
  10081. // Don't update if the series exists in nav and we have disabled
  10082. // adaptToUpdatedData.
  10083. if (linkedNavSeries &&
  10084. navigator.navigatorOptions.adaptToUpdatedData === false) {
  10085. return;
  10086. }
  10087. navSeriesMixin.name = 'Navigator ' + baseSeries.length;
  10088. baseOptions = base.options || {};
  10089. baseNavigatorOptions = baseOptions.navigatorOptions || {};
  10090. mergedNavSeriesOptions = merge(baseOptions, navSeriesMixin, userNavOptions, baseNavigatorOptions);
  10091. // Once nav series type is resolved, pick correct pointRange
  10092. mergedNavSeriesOptions.pointRange = pick(
  10093. // Stricte set pointRange in options
  10094. userNavOptions.pointRange, baseNavigatorOptions.pointRange,
  10095. // Fallback to default values, e.g. `null` for column
  10096. defaultOptions.plotOptions[mergedNavSeriesOptions.type || 'line'].pointRange);
  10097. // Merge data separately. Do a slice to avoid mutating the
  10098. // navigator options from base series (#4923).
  10099. var navigatorSeriesData = baseNavigatorOptions.data || userNavOptions.data;
  10100. navigator.hasNavigatorData =
  10101. navigator.hasNavigatorData || !!navigatorSeriesData;
  10102. mergedNavSeriesOptions.data =
  10103. navigatorSeriesData ||
  10104. baseOptions.data && baseOptions.data.slice(0);
  10105. // Update or add the series
  10106. if (linkedNavSeries && linkedNavSeries.options) {
  10107. linkedNavSeries.update(mergedNavSeriesOptions, redraw);
  10108. }
  10109. else {
  10110. base.navigatorSeries = chart.initSeries(mergedNavSeriesOptions);
  10111. base.navigatorSeries.baseSeries = base; // Store ref
  10112. navigatorSeries.push(base.navigatorSeries);
  10113. }
  10114. });
  10115. }
  10116. // If user has defined data (and no base series) or explicitly defined
  10117. // navigator.series as an array, we create these series on top of any
  10118. // base series.
  10119. if (chartNavigatorSeriesOptions.data &&
  10120. !(baseSeries && baseSeries.length) ||
  10121. isArray(chartNavigatorSeriesOptions)) {
  10122. navigator.hasNavigatorData = false;
  10123. // Allow navigator.series to be an array
  10124. chartNavigatorSeriesOptions =
  10125. splat(chartNavigatorSeriesOptions);
  10126. chartNavigatorSeriesOptions.forEach(function (userSeriesOptions, i) {
  10127. navSeriesMixin.name =
  10128. 'Navigator ' + (navigatorSeries.length + 1);
  10129. mergedNavSeriesOptions = merge(defaultOptions.navigator.series, {
  10130. // Since we don't have a base series to pull color from,
  10131. // try to fake it by using color from series with same
  10132. // index. Otherwise pull from the colors array. We need
  10133. // an explicit color as otherwise updates will increment
  10134. // color counter and we'll get a new color for each
  10135. // update of the nav series.
  10136. color: chart.series[i] &&
  10137. !chart.series[i].options.isInternal &&
  10138. chart.series[i].color ||
  10139. chart.options.colors[i] ||
  10140. chart.options.colors[0]
  10141. }, navSeriesMixin, userSeriesOptions);
  10142. mergedNavSeriesOptions.data = userSeriesOptions.data;
  10143. if (mergedNavSeriesOptions.data) {
  10144. navigator.hasNavigatorData = true;
  10145. navigatorSeries.push(chart.initSeries(mergedNavSeriesOptions));
  10146. }
  10147. });
  10148. }
  10149. if (addEvents) {
  10150. this.addBaseSeriesEvents();
  10151. }
  10152. };
  10153. /**
  10154. * Add data events.
  10155. * For example when main series is updated we need to recalculate extremes
  10156. *
  10157. * @private
  10158. * @function Highcharts.Navigator#addBaseSeriesEvent
  10159. * @return {void}
  10160. */
  10161. Navigator.prototype.addBaseSeriesEvents = function () {
  10162. var navigator = this, baseSeries = navigator.baseSeries || [];
  10163. // Bind modified extremes event to first base's xAxis only.
  10164. // In event of > 1 base-xAxes, the navigator will ignore those.
  10165. // Adding this multiple times to the same axis is no problem, as
  10166. // duplicates should be discarded by the browser.
  10167. if (baseSeries[0] && baseSeries[0].xAxis) {
  10168. addEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes);
  10169. }
  10170. baseSeries.forEach(function (base) {
  10171. // Link base series show/hide to navigator series visibility
  10172. addEvent(base, 'show', function () {
  10173. if (this.navigatorSeries) {
  10174. this.navigatorSeries.setVisible(true, false);
  10175. }
  10176. });
  10177. addEvent(base, 'hide', function () {
  10178. if (this.navigatorSeries) {
  10179. this.navigatorSeries.setVisible(false, false);
  10180. }
  10181. });
  10182. // Respond to updated data in the base series, unless explicitily
  10183. // not adapting to data changes.
  10184. if (this.navigatorOptions.adaptToUpdatedData !== false) {
  10185. if (base.xAxis) {
  10186. addEvent(base, 'updatedData', this.updatedDataHandler);
  10187. }
  10188. }
  10189. // Handle series removal
  10190. addEvent(base, 'remove', function () {
  10191. if (this.navigatorSeries) {
  10192. erase(navigator.series, this.navigatorSeries);
  10193. if (defined(this.navigatorSeries.options)) {
  10194. this.navigatorSeries.remove(false);
  10195. }
  10196. delete this.navigatorSeries;
  10197. }
  10198. });
  10199. }, this);
  10200. };
  10201. /**
  10202. * Get minimum from all base series connected to the navigator
  10203. * @private
  10204. * @param {number} currentSeriesMin
  10205. * Minium from the current series
  10206. * @return {number} Minimum from all series
  10207. */
  10208. Navigator.prototype.getBaseSeriesMin = function (currentSeriesMin) {
  10209. return this.baseSeries.reduce(function (min, series) {
  10210. // (#10193)
  10211. return Math.min(min, series.xData ? series.xData[0] : min);
  10212. }, currentSeriesMin);
  10213. };
  10214. /**
  10215. * Set the navigator x axis extremes to reflect the total. The navigator
  10216. * extremes should always be the extremes of the union of all series in the
  10217. * chart as well as the navigator series.
  10218. *
  10219. * @private
  10220. * @function Highcharts.Navigator#modifyNavigatorAxisExtremes
  10221. */
  10222. Navigator.prototype.modifyNavigatorAxisExtremes = function () {
  10223. var xAxis = this.xAxis, unionExtremes;
  10224. if (typeof xAxis.getExtremes !== 'undefined') {
  10225. unionExtremes = this.getUnionExtremes(true);
  10226. if (unionExtremes &&
  10227. (unionExtremes.dataMin !== xAxis.min ||
  10228. unionExtremes.dataMax !== xAxis.max)) {
  10229. xAxis.min = unionExtremes.dataMin;
  10230. xAxis.max = unionExtremes.dataMax;
  10231. }
  10232. }
  10233. };
  10234. /**
  10235. * Hook to modify the base axis extremes with information from the Navigator
  10236. *
  10237. * @private
  10238. * @function Highcharts.Navigator#modifyBaseAxisExtremes
  10239. */
  10240. Navigator.prototype.modifyBaseAxisExtremes = function () {
  10241. var baseXAxis = this, navigator = baseXAxis.chart.navigator, baseExtremes = baseXAxis.getExtremes(), baseMin = baseExtremes.min, baseMax = baseExtremes.max, baseDataMin = baseExtremes.dataMin, baseDataMax = baseExtremes.dataMax, range = baseMax - baseMin, stickToMin = navigator.stickToMin, stickToMax = navigator.stickToMax, overscroll = pick(baseXAxis.options.overscroll, 0), newMax, newMin, navigatorSeries = navigator.series && navigator.series[0], hasSetExtremes = !!baseXAxis.setExtremes,
  10242. // When the extremes have been set by range selector button, don't
  10243. // stick to min or max. The range selector buttons will handle the
  10244. // extremes. (#5489)
  10245. unmutable = baseXAxis.eventArgs &&
  10246. baseXAxis.eventArgs.trigger === 'rangeSelectorButton';
  10247. if (!unmutable) {
  10248. // If the zoomed range is already at the min, move it to the right
  10249. // as new data comes in
  10250. if (stickToMin) {
  10251. newMin = baseDataMin;
  10252. newMax = newMin + range;
  10253. }
  10254. // If the zoomed range is already at the max, move it to the right
  10255. // as new data comes in
  10256. if (stickToMax) {
  10257. newMax = baseDataMax + overscroll;
  10258. // If stickToMin is true, the new min value is set above
  10259. if (!stickToMin) {
  10260. newMin = Math.max(baseDataMin, // don't go below data extremes (#13184)
  10261. newMax - range, navigator.getBaseSeriesMin(navigatorSeries && navigatorSeries.xData ?
  10262. navigatorSeries.xData[0] :
  10263. -Number.MAX_VALUE));
  10264. }
  10265. }
  10266. // Update the extremes
  10267. if (hasSetExtremes && (stickToMin || stickToMax)) {
  10268. if (isNumber(newMin)) {
  10269. baseXAxis.min = baseXAxis.userMin = newMin;
  10270. baseXAxis.max = baseXAxis.userMax = newMax;
  10271. }
  10272. }
  10273. }
  10274. // Reset
  10275. navigator.stickToMin =
  10276. navigator.stickToMax = null;
  10277. };
  10278. /**
  10279. * Handler for updated data on the base series. When data is modified, the
  10280. * navigator series must reflect it. This is called from the Chart.redraw
  10281. * function before axis and series extremes are computed.
  10282. *
  10283. * @private
  10284. * @function Highcharts.Navigator#updateDataHandler
  10285. */
  10286. Navigator.prototype.updatedDataHandler = function () {
  10287. var navigator = this.chart.navigator, baseSeries = this, navigatorSeries = this.navigatorSeries, xDataMin = navigator.getBaseSeriesMin(baseSeries.xData[0]);
  10288. // If the scrollbar is scrolled all the way to the right, keep right as
  10289. // new data comes in.
  10290. navigator.stickToMax = navigator.reversedExtremes ?
  10291. Math.round(navigator.zoomedMin) === 0 :
  10292. Math.round(navigator.zoomedMax) >= Math.round(navigator.size);
  10293. // Detect whether the zoomed area should stick to the minimum or
  10294. // maximum. If the current axis minimum falls outside the new updated
  10295. // dataset, we must adjust.
  10296. navigator.stickToMin = isNumber(baseSeries.xAxis.min) &&
  10297. (baseSeries.xAxis.min <= xDataMin) &&
  10298. (!this.chart.fixedRange || !navigator.stickToMax);
  10299. // Set the navigator series data to the new data of the base series
  10300. if (navigatorSeries && !navigator.hasNavigatorData) {
  10301. navigatorSeries.options.pointStart = baseSeries.xData[0];
  10302. navigatorSeries.setData(baseSeries.options.data, false, null, false); // #5414
  10303. }
  10304. };
  10305. /**
  10306. * Add chart events, like redrawing navigator, when chart requires that.
  10307. *
  10308. * @private
  10309. * @function Highcharts.Navigator#addChartEvents
  10310. * @return {void}
  10311. */
  10312. Navigator.prototype.addChartEvents = function () {
  10313. if (!this.eventsToUnbind) {
  10314. this.eventsToUnbind = [];
  10315. }
  10316. this.eventsToUnbind.push(
  10317. // Move the scrollbar after redraw, like after data updata even if
  10318. // axes don't redraw
  10319. addEvent(this.chart, 'redraw', function () {
  10320. var navigator = this.navigator, xAxis = navigator && (navigator.baseSeries &&
  10321. navigator.baseSeries[0] &&
  10322. navigator.baseSeries[0].xAxis ||
  10323. this.xAxis[0]); // #5709, #13114
  10324. if (xAxis) {
  10325. navigator.render(xAxis.min, xAxis.max);
  10326. }
  10327. }),
  10328. // Make room for the navigator, can be placed around the chart:
  10329. addEvent(this.chart, 'getMargins', function () {
  10330. var chart = this, navigator = chart.navigator, marginName = navigator.opposite ?
  10331. 'plotTop' : 'marginBottom';
  10332. if (chart.inverted) {
  10333. marginName = navigator.opposite ?
  10334. 'marginRight' : 'plotLeft';
  10335. }
  10336. chart[marginName] =
  10337. (chart[marginName] || 0) + (navigator.navigatorEnabled || !chart.inverted ?
  10338. navigator.outlineHeight :
  10339. 0) + navigator.navigatorOptions.margin;
  10340. }));
  10341. };
  10342. /**
  10343. * Destroys allocated elements.
  10344. *
  10345. * @private
  10346. * @function Highcharts.Navigator#destroy
  10347. */
  10348. Navigator.prototype.destroy = function () {
  10349. // Disconnect events added in addEvents
  10350. this.removeEvents();
  10351. if (this.xAxis) {
  10352. erase(this.chart.xAxis, this.xAxis);
  10353. erase(this.chart.axes, this.xAxis);
  10354. }
  10355. if (this.yAxis) {
  10356. erase(this.chart.yAxis, this.yAxis);
  10357. erase(this.chart.axes, this.yAxis);
  10358. }
  10359. // Destroy series
  10360. (this.series || []).forEach(function (s) {
  10361. if (s.destroy) {
  10362. s.destroy();
  10363. }
  10364. });
  10365. // Destroy properties
  10366. [
  10367. 'series', 'xAxis', 'yAxis', 'shades', 'outline', 'scrollbarTrack',
  10368. 'scrollbarRifles', 'scrollbarGroup', 'scrollbar', 'navigatorGroup',
  10369. 'rendered'
  10370. ].forEach(function (prop) {
  10371. if (this[prop] && this[prop].destroy) {
  10372. this[prop].destroy();
  10373. }
  10374. this[prop] = null;
  10375. }, this);
  10376. // Destroy elements in collection
  10377. [this.handles].forEach(function (coll) {
  10378. destroyObjectProperties(coll);
  10379. }, this);
  10380. };
  10381. return Navigator;
  10382. }());
  10383. // End of prototype
  10384. if (!H.Navigator) {
  10385. H.Navigator = Navigator;
  10386. NavigatorAxis.compose(Axis);
  10387. // For Stock charts. For x only zooming, do not to create the zoom button
  10388. // because X axis zooming is already allowed by the Navigator and Range
  10389. // selector. (#9285)
  10390. addEvent(Chart, 'beforeShowResetZoom', function () {
  10391. var chartOptions = this.options, navigator = chartOptions.navigator, rangeSelector = chartOptions.rangeSelector;
  10392. if (((navigator && navigator.enabled) ||
  10393. (rangeSelector && rangeSelector.enabled)) &&
  10394. ((!isTouchDevice && chartOptions.chart.zoomType === 'x') ||
  10395. (isTouchDevice && chartOptions.chart.pinchType === 'x'))) {
  10396. return false;
  10397. }
  10398. });
  10399. // Initialize navigator for stock charts
  10400. addEvent(Chart, 'beforeRender', function () {
  10401. var options = this.options;
  10402. if (options.navigator.enabled ||
  10403. options.scrollbar.enabled) {
  10404. this.scroller = this.navigator = new Navigator(this);
  10405. }
  10406. });
  10407. // For stock charts, extend the Chart.setChartSize method so that we can set
  10408. // the final top position of the navigator once the height of the chart,
  10409. // including the legend, is determined. #367. We can't use Chart.getMargins,
  10410. // because labels offsets are not calculated yet.
  10411. addEvent(Chart, 'afterSetChartSize', function () {
  10412. var legend = this.legend, navigator = this.navigator, scrollbarHeight, legendOptions, xAxis, yAxis;
  10413. if (navigator) {
  10414. legendOptions = legend && legend.options;
  10415. xAxis = navigator.xAxis;
  10416. yAxis = navigator.yAxis;
  10417. scrollbarHeight = navigator.scrollbarHeight;
  10418. // Compute the top position
  10419. if (this.inverted) {
  10420. navigator.left = navigator.opposite ?
  10421. this.chartWidth - scrollbarHeight -
  10422. navigator.height :
  10423. this.spacing[3] + scrollbarHeight;
  10424. navigator.top = this.plotTop + scrollbarHeight;
  10425. }
  10426. else {
  10427. navigator.left = this.plotLeft + scrollbarHeight;
  10428. navigator.top = navigator.navigatorOptions.top ||
  10429. this.chartHeight -
  10430. navigator.height -
  10431. scrollbarHeight -
  10432. this.spacing[2] -
  10433. (this.rangeSelector && this.extraBottomMargin ?
  10434. this.rangeSelector.getHeight() :
  10435. 0) -
  10436. ((legendOptions &&
  10437. legendOptions.verticalAlign === 'bottom' &&
  10438. legendOptions.layout !== 'proximate' && // #13392
  10439. legendOptions.enabled &&
  10440. !legendOptions.floating) ?
  10441. legend.legendHeight +
  10442. pick(legendOptions.margin, 10) :
  10443. 0) -
  10444. (this.titleOffset ? this.titleOffset[2] : 0);
  10445. }
  10446. if (xAxis && yAxis) { // false if navigator is disabled (#904)
  10447. if (this.inverted) {
  10448. xAxis.options.left = yAxis.options.left = navigator.left;
  10449. }
  10450. else {
  10451. xAxis.options.top = yAxis.options.top = navigator.top;
  10452. }
  10453. xAxis.setAxisSize();
  10454. yAxis.setAxisSize();
  10455. }
  10456. }
  10457. });
  10458. // Merge options, if no scrolling exists yet
  10459. addEvent(Chart, 'update', function (e) {
  10460. var navigatorOptions = (e.options.navigator || {}), scrollbarOptions = (e.options.scrollbar || {});
  10461. if (!this.navigator && !this.scroller &&
  10462. (navigatorOptions.enabled || scrollbarOptions.enabled)) {
  10463. merge(true, this.options.navigator, navigatorOptions);
  10464. merge(true, this.options.scrollbar, scrollbarOptions);
  10465. delete e.options.navigator;
  10466. delete e.options.scrollbar;
  10467. }
  10468. });
  10469. // Initialize navigator, if no scrolling exists yet
  10470. addEvent(Chart, 'afterUpdate', function (event) {
  10471. if (!this.navigator && !this.scroller &&
  10472. (this.options.navigator.enabled ||
  10473. this.options.scrollbar.enabled)) {
  10474. this.scroller = this.navigator = new Navigator(this);
  10475. if (pick(event.redraw, true)) {
  10476. this.redraw(event.animation); // #7067
  10477. }
  10478. }
  10479. });
  10480. // Handle adding new series
  10481. addEvent(Chart, 'afterAddSeries', function () {
  10482. if (this.navigator) {
  10483. // Recompute which series should be shown in navigator, and add them
  10484. this.navigator.setBaseSeries(null, false);
  10485. }
  10486. });
  10487. // Handle updating series
  10488. addEvent(Series, 'afterUpdate', function () {
  10489. if (this.chart.navigator && !this.options.isInternal) {
  10490. this.chart.navigator.setBaseSeries(null, false);
  10491. }
  10492. });
  10493. Chart.prototype.callbacks.push(function (chart) {
  10494. var extremes, navigator = chart.navigator;
  10495. // Initialize the navigator
  10496. if (navigator && chart.xAxis[0]) {
  10497. extremes = chart.xAxis[0].getExtremes();
  10498. navigator.render(extremes.min, extremes.max);
  10499. }
  10500. });
  10501. }
  10502. H.Navigator = Navigator;
  10503. return H.Navigator;
  10504. });
  10505. _registerModule(_modules, 'masters/modules/gantt.src.js', [], function () {
  10506. });
  10507. }));