annotations.src.js 235 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357
  1. /**
  2. * @license Highcharts JS v8.0.0 (2019-12-10)
  3. *
  4. * Annotations module
  5. *
  6. * (c) 2009-2019 Torstein Honsi
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. factory['default'] = factory;
  14. module.exports = factory;
  15. } else if (typeof define === 'function' && define.amd) {
  16. define('highcharts/modules/annotations', ['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, 'annotations/eventEmitterMixin.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
  32. var objectEach = U.objectEach,
  33. pick = U.pick;
  34. var fireEvent = H.fireEvent;
  35. /**
  36. * It provides methods for:
  37. * - adding and handling DOM events and a drag event,
  38. * - mapping a mouse move event to the distance between two following events.
  39. * The units of the distance are specific to a transformation,
  40. * e.g. for rotation they are radians, for scaling they are scale factors.
  41. *
  42. * @private
  43. * @mixin
  44. * @memberOf Annotation
  45. */
  46. var eventEmitterMixin = {
  47. /**
  48. * Add emitter events.
  49. */
  50. addEvents: function () {
  51. var emitter = this;
  52. H.addEvent(
  53. emitter.graphic.element,
  54. 'mousedown',
  55. function (e) {
  56. emitter.onMouseDown(e);
  57. }
  58. );
  59. objectEach(emitter.options.events, function (event, type) {
  60. var eventHandler = function (e) {
  61. if (type !== 'click' || !emitter.cancelClick) {
  62. event.call(
  63. emitter,
  64. emitter.chart.pointer.normalize(e),
  65. emitter.target
  66. );
  67. }
  68. };
  69. if (H.inArray(type, emitter.nonDOMEvents || []) === -1) {
  70. emitter.graphic.on(type, eventHandler);
  71. } else {
  72. H.addEvent(emitter, type, eventHandler);
  73. }
  74. });
  75. if (emitter.options.draggable) {
  76. H.addEvent(emitter, 'drag', emitter.onDrag);
  77. if (!emitter.graphic.renderer.styledMode) {
  78. emitter.graphic.css({
  79. cursor: {
  80. x: 'ew-resize',
  81. y: 'ns-resize',
  82. xy: 'move'
  83. }[emitter.options.draggable]
  84. });
  85. }
  86. }
  87. if (!emitter.isUpdating) {
  88. fireEvent(emitter, 'add');
  89. }
  90. },
  91. /**
  92. * Remove emitter document events.
  93. */
  94. removeDocEvents: function () {
  95. if (this.removeDrag) {
  96. this.removeDrag = this.removeDrag();
  97. }
  98. if (this.removeMouseUp) {
  99. this.removeMouseUp = this.removeMouseUp();
  100. }
  101. },
  102. /**
  103. * Mouse down handler.
  104. *
  105. * @param {Object} e event
  106. */
  107. onMouseDown: function (e) {
  108. var emitter = this,
  109. pointer = emitter.chart.pointer,
  110. prevChartX,
  111. prevChartY;
  112. if (e.preventDefault) {
  113. e.preventDefault();
  114. }
  115. // On right click, do nothing:
  116. if (e.button === 2) {
  117. return;
  118. }
  119. e = pointer.normalize(e);
  120. prevChartX = e.chartX;
  121. prevChartY = e.chartY;
  122. emitter.cancelClick = false;
  123. emitter.chart.hasDraggedAnnotation = true;
  124. emitter.removeDrag = H.addEvent(
  125. H.doc,
  126. 'mousemove',
  127. function (e) {
  128. emitter.hasDragged = true;
  129. e = pointer.normalize(e);
  130. e.prevChartX = prevChartX;
  131. e.prevChartY = prevChartY;
  132. fireEvent(emitter, 'drag', e);
  133. prevChartX = e.chartX;
  134. prevChartY = e.chartY;
  135. }
  136. );
  137. emitter.removeMouseUp = H.addEvent(
  138. H.doc,
  139. 'mouseup',
  140. function (e) {
  141. emitter.cancelClick = emitter.hasDragged;
  142. emitter.hasDragged = false;
  143. emitter.chart.hasDraggedAnnotation = false;
  144. // ControlPoints vs Annotation:
  145. fireEvent(pick(emitter.target, emitter), 'afterUpdate');
  146. emitter.onMouseUp(e);
  147. }
  148. );
  149. },
  150. /**
  151. * Mouse up handler.
  152. *
  153. * @param {Object} e event
  154. */
  155. onMouseUp: function () {
  156. var chart = this.chart,
  157. annotation = this.target || this,
  158. annotationsOptions = chart.options.annotations,
  159. index = chart.annotations.indexOf(annotation);
  160. this.removeDocEvents();
  161. annotationsOptions[index] = annotation.options;
  162. },
  163. /**
  164. * Drag and drop event. All basic annotations should share this
  165. * capability as well as the extended ones.
  166. *
  167. * @param {Object} e event
  168. */
  169. onDrag: function (e) {
  170. if (
  171. this.chart.isInsidePlot(
  172. e.chartX - this.chart.plotLeft,
  173. e.chartY - this.chart.plotTop
  174. )
  175. ) {
  176. var translation = this.mouseMoveToTranslation(e);
  177. if (this.options.draggable === 'x') {
  178. translation.y = 0;
  179. }
  180. if (this.options.draggable === 'y') {
  181. translation.x = 0;
  182. }
  183. if (this.points.length) {
  184. this.translate(translation.x, translation.y);
  185. } else {
  186. this.shapes.forEach(function (shape) {
  187. shape.translate(translation.x, translation.y);
  188. });
  189. this.labels.forEach(function (label) {
  190. label.translate(translation.x, translation.y);
  191. });
  192. }
  193. this.redraw(false);
  194. }
  195. },
  196. /**
  197. * Map mouse move event to the radians.
  198. *
  199. * @param {Object} e event
  200. * @param {number} cx center x
  201. * @param {number} cy center y
  202. */
  203. mouseMoveToRadians: function (e, cx, cy) {
  204. var prevDy = e.prevChartY - cy,
  205. prevDx = e.prevChartX - cx,
  206. dy = e.chartY - cy,
  207. dx = e.chartX - cx,
  208. temp;
  209. if (this.chart.inverted) {
  210. temp = prevDx;
  211. prevDx = prevDy;
  212. prevDy = temp;
  213. temp = dx;
  214. dx = dy;
  215. dy = temp;
  216. }
  217. return Math.atan2(dy, dx) - Math.atan2(prevDy, prevDx);
  218. },
  219. /**
  220. * Map mouse move event to the distance between two following events.
  221. *
  222. * @param {Object} e event
  223. */
  224. mouseMoveToTranslation: function (e) {
  225. var dx = e.chartX - e.prevChartX,
  226. dy = e.chartY - e.prevChartY,
  227. temp;
  228. if (this.chart.inverted) {
  229. temp = dy;
  230. dy = dx;
  231. dx = temp;
  232. }
  233. return {
  234. x: dx,
  235. y: dy
  236. };
  237. },
  238. /**
  239. * Map mouse move to the scale factors.
  240. *
  241. * @param {Object} e event
  242. * @param {number} cx center x
  243. * @param {number} cy center y
  244. **/
  245. mouseMoveToScale: function (e, cx, cy) {
  246. var prevDx = e.prevChartX - cx,
  247. prevDy = e.prevChartY - cy,
  248. dx = e.chartX - cx,
  249. dy = e.chartY - cy,
  250. sx = (dx || 1) / (prevDx || 1),
  251. sy = (dy || 1) / (prevDy || 1),
  252. temp;
  253. if (this.chart.inverted) {
  254. temp = sy;
  255. sy = sx;
  256. sx = temp;
  257. }
  258. return {
  259. x: sx,
  260. y: sy
  261. };
  262. },
  263. /**
  264. * Destroy the event emitter.
  265. */
  266. destroy: function () {
  267. this.removeDocEvents();
  268. H.removeEvent(this);
  269. this.hcEvents = null;
  270. }
  271. };
  272. return eventEmitterMixin;
  273. });
  274. _registerModule(_modules, 'annotations/ControlPoint.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['annotations/eventEmitterMixin.js']], function (H, U, eventEmitterMixin) {
  275. var extend = U.extend,
  276. pick = U.pick;
  277. /**
  278. * A control point class which is a connection between controllable
  279. * transform methods and a user actions.
  280. *
  281. * @constructor
  282. * @mixes eventEmitterMixin
  283. * @memberOf Annotation
  284. *
  285. * @param {Highcharts.Chart} chart a chart instance
  286. * @param {Object} target a controllable instance which is a target for
  287. * a control point
  288. * @param {Annotation.ControlPoint.Options} options an options object
  289. * @param {number} [index]
  290. */
  291. function ControlPoint(chart, target, options, index) {
  292. this.chart = chart;
  293. this.target = target;
  294. this.options = options;
  295. this.index = pick(options.index, index);
  296. }
  297. /**
  298. * @typedef {Object} Annotation.ControlPoint.Position
  299. * @property {number} x
  300. * @property {number} y
  301. */
  302. /**
  303. * @callback Annotation.ControlPoint.Positioner
  304. * @param {Object} e event
  305. * @param {Controllable} target
  306. * @return {Annotation.ControlPoint.Position} position
  307. */
  308. /**
  309. * @typedef {Object} Annotation.ControlPoint.Options
  310. * @property {string} symbol
  311. * @property {number} width
  312. * @property {number} height
  313. * @property {Object} style
  314. * @property {boolean} visible
  315. * @property {Annotation.ControlPoint.Positioner} positioner
  316. * @property {Object} events
  317. */
  318. extend(
  319. ControlPoint.prototype,
  320. eventEmitterMixin
  321. );
  322. /**
  323. * List of events for `anntation.options.events` that should not be
  324. * added to `annotation.graphic` but to the `annotation`.
  325. *
  326. * @type {Array<string>}
  327. */
  328. ControlPoint.prototype.nonDOMEvents = ['drag'];
  329. /**
  330. * Set the visibility.
  331. *
  332. * @param {boolean} [visible]
  333. **/
  334. ControlPoint.prototype.setVisibility = function (visible) {
  335. this.graphic.attr('visibility', visible ? 'visible' : 'hidden');
  336. this.options.visible = visible;
  337. };
  338. /**
  339. * Render the control point.
  340. */
  341. ControlPoint.prototype.render = function () {
  342. var chart = this.chart,
  343. options = this.options;
  344. this.graphic = chart.renderer
  345. .symbol(
  346. options.symbol,
  347. 0,
  348. 0,
  349. options.width,
  350. options.height
  351. )
  352. .add(chart.controlPointsGroup)
  353. .css(options.style);
  354. this.setVisibility(options.visible);
  355. this.addEvents();
  356. };
  357. /**
  358. * Redraw the control point.
  359. *
  360. * @param {boolean} [animation]
  361. */
  362. ControlPoint.prototype.redraw = function (animation) {
  363. this.graphic[animation ? 'animate' : 'attr'](
  364. this.options.positioner.call(this, this.target)
  365. );
  366. };
  367. /**
  368. * Destroy the control point.
  369. */
  370. ControlPoint.prototype.destroy = function () {
  371. eventEmitterMixin.destroy.call(this);
  372. if (this.graphic) {
  373. this.graphic = this.graphic.destroy();
  374. }
  375. this.chart = null;
  376. this.target = null;
  377. this.options = null;
  378. };
  379. /**
  380. * Update the control point.
  381. */
  382. ControlPoint.prototype.update = function (userOptions) {
  383. var chart = this.chart,
  384. target = this.target,
  385. index = this.index,
  386. options = H.merge(true, this.options, userOptions);
  387. this.destroy();
  388. this.constructor(chart, target, options, index);
  389. this.render(chart.controlPointsGroup);
  390. this.redraw();
  391. };
  392. return ControlPoint;
  393. });
  394. _registerModule(_modules, 'annotations/MockPoint.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
  395. var defined = U.defined,
  396. extend = U.extend;
  397. /**
  398. * A mock point label configuration.
  399. *
  400. * @interface Annotation.MockLabelOptionsObject
  401. *//**
  402. * X value translated to x axis scale
  403. * @name Annotation.MockLabelOptionsObject#x
  404. * @type {number|undefined}
  405. *//**
  406. * Y value translated to y axis scale
  407. * @name Annotation.MockLabelOptionsObject#y
  408. * @type {number|undefined}
  409. *//**
  410. * @name Annotation.MockLabelOptionsObject#point
  411. * @type {Highcharts.Point}
  412. */
  413. /**
  414. * A mock point configuration.
  415. *
  416. * @interface Highcharts.MockPointOptionsObject
  417. *//**
  418. * x value for the point in xAxis scale or pixels
  419. * @name Highcharts.MockPointOptionsObject#x
  420. * @type {number}
  421. *//**
  422. * y value for the point in yAxis scale or pixels
  423. * @name Highcharts.MockPointOptionsObject#y
  424. * @type {number}
  425. *//**
  426. * xAxis index or id
  427. * @name Highcharts.MockPointOptionsObject#xAxis
  428. * @type {Highcharts.Axis|number|string|undefined}
  429. *//**
  430. * yAxis index or id
  431. * @name Highcharts.MockPointOptionsObject#yAxis
  432. * @property {Highcharts.Axis|number|string|undefined}
  433. */
  434. /**
  435. * A point-like object, a mock point or a point uses in series.
  436. *
  437. * @private
  438. * @typedef {Highcharts.Point|Highcharts.MockPoint} Highcharts.PointLike
  439. */
  440. /**
  441. * A trimmed point object which imitates {@link Highchart.Point} class.
  442. * It is created when there is a need of pointing to some chart's position
  443. * using axis values or pixel values
  444. *
  445. * @private
  446. * @class
  447. * @name Highcharts.MockPoint
  448. *
  449. * @param {Highcharts.Chart} chart
  450. * The chart object
  451. *
  452. * @param {Highcharts.MockPointOptionsObject} options
  453. * The options object
  454. */
  455. function MockPoint(chart, target, options) {
  456. /**
  457. * A mock series instance imitating a real series from a real point.
  458. *
  459. * @type {Object}
  460. * @property {boolean} series.visible=true - whether a series is visible
  461. * @property {Chart} series.chart - a chart instance
  462. * @property {function} series.getPlotBox
  463. */
  464. this.series = {
  465. visible: true,
  466. chart: chart,
  467. getPlotBox: H.Series.prototype.getPlotBox
  468. };
  469. /**
  470. * @type {?Controllable}
  471. */
  472. this.target = target || null;
  473. /**
  474. * Options for the mock point.
  475. *
  476. * @type {Highcharts.MockPointOptionsObject}
  477. */
  478. this.options = options;
  479. /**
  480. * If an xAxis is set it represents the point's value in terms of the xAxis.
  481. *
  482. * @name Annotation.MockPoint#x
  483. * @type {?number}
  484. */
  485. /**
  486. * If an yAxis is set it represents the point's value in terms of the yAxis.
  487. *
  488. * @name Annotation.MockPoint#y
  489. * @type {?number}
  490. */
  491. /**
  492. * It represents the point's pixel x coordinate relative to its plot box.
  493. *
  494. * @name Annotation.MockPoint#plotX
  495. * @type {?number}
  496. */
  497. /**
  498. * It represents the point's pixel y position relative to its plot box.
  499. *
  500. * @name Annotation.MockPoint#plotY
  501. * @type {?number}
  502. */
  503. /**
  504. * Whether the point is inside the plot box.
  505. *
  506. * @name Annotation.MockPoint#isInside
  507. * @type {boolean}
  508. */
  509. this.applyOptions(this.getOptions());
  510. }
  511. /**
  512. * Create a mock point from a real Highcharts point.
  513. *
  514. * @param {Point} point
  515. *
  516. * @return {Annotation.MockPoint} a mock point instance.
  517. */
  518. MockPoint.fromPoint = function (point) {
  519. return new MockPoint(point.series.chart, null, {
  520. x: point.x,
  521. y: point.y,
  522. xAxis: point.series.xAxis,
  523. yAxis: point.series.yAxis
  524. });
  525. };
  526. /**
  527. * @typedef Annotation.MockPoint.Position
  528. * @property {number} x
  529. * @property {number} y
  530. */
  531. /**
  532. * Get the pixel position from the point like object.
  533. *
  534. * @param {Annotation.PointLike} point
  535. * @param {boolean} [paneCoordinates]
  536. * whether the pixel position should be relative
  537. *
  538. * @return {Annotation.MockPoint.Position} pixel position
  539. */
  540. MockPoint.pointToPixels = function (point, paneCoordinates) {
  541. var series = point.series,
  542. chart = series.chart,
  543. x = point.plotX,
  544. y = point.plotY,
  545. plotBox;
  546. if (chart.inverted) {
  547. if (point.mock) {
  548. x = point.plotY;
  549. y = point.plotX;
  550. } else {
  551. x = chart.plotWidth - point.plotY;
  552. y = chart.plotHeight - point.plotX;
  553. }
  554. }
  555. if (series && !paneCoordinates) {
  556. plotBox = series.getPlotBox();
  557. x += plotBox.translateX;
  558. y += plotBox.translateY;
  559. }
  560. return {
  561. x: x,
  562. y: y
  563. };
  564. };
  565. /**
  566. * Get fresh mock point options from the point like object.
  567. *
  568. * @param {Annotation.PointLike} point
  569. *
  570. * @return {Annotation.MockPoint.Options} mock point's options
  571. */
  572. MockPoint.pointToOptions = function (point) {
  573. return {
  574. x: point.x,
  575. y: point.y,
  576. xAxis: point.series.xAxis,
  577. yAxis: point.series.yAxis
  578. };
  579. };
  580. extend(MockPoint.prototype, /** @lends Annotation.MockPoint# */ {
  581. /**
  582. * A flag indicating that a point is not the real one.
  583. *
  584. * @type {boolean}
  585. * @default true
  586. */
  587. mock: true,
  588. /**
  589. * Check if the point has dynamic options.
  590. *
  591. * @return {boolean} A positive flag if the point has dynamic options.
  592. */
  593. hasDynamicOptions: function () {
  594. return typeof this.options === 'function';
  595. },
  596. /**
  597. * Get the point's options.
  598. *
  599. * @return {Annotation.MockPoint.Options} the mock point's options.
  600. */
  601. getOptions: function () {
  602. return this.hasDynamicOptions() ?
  603. this.options(this.target) :
  604. this.options;
  605. },
  606. /**
  607. * Apply options for the point.
  608. *
  609. * @param {Annotation.MockPoint.Options} options
  610. */
  611. applyOptions: function (options) {
  612. this.command = options.command;
  613. this.setAxis(options, 'x');
  614. this.setAxis(options, 'y');
  615. this.refresh();
  616. },
  617. /**
  618. * Set x or y axis.
  619. *
  620. * @param {Annotation.MockPoint.Options} options
  621. * @param {string} xOrY 'x' or 'y' string literal
  622. */
  623. setAxis: function (options, xOrY) {
  624. var axisName = xOrY + 'Axis',
  625. axisOptions = options[axisName],
  626. chart = this.series.chart;
  627. this.series[axisName] =
  628. axisOptions instanceof H.Axis ?
  629. axisOptions :
  630. defined(axisOptions) ?
  631. chart[axisName][axisOptions] || chart.get(axisOptions) :
  632. null;
  633. },
  634. /**
  635. * Transform the mock point to an anchor
  636. * (relative position on the chart).
  637. *
  638. * @return {Array<number>} A quadruple of numbers which denotes x, y,
  639. * width and height of the box
  640. **/
  641. toAnchor: function () {
  642. var anchor = [this.plotX, this.plotY, 0, 0];
  643. if (this.series.chart.inverted) {
  644. anchor[0] = this.plotY;
  645. anchor[1] = this.plotX;
  646. }
  647. return anchor;
  648. },
  649. /**
  650. * @typedef {Object} Annotation.MockPoint.LabelConfig
  651. * @property {number|undefined} x x value translated to x axis scale
  652. * @property {number|undefined} y y value translated to y axis scale
  653. * @property {Annotation.MockPoint} point instance of the point
  654. */
  655. /**
  656. * Returns a label config object -
  657. * the same as Highcharts.Point.prototype.getLabelConfig
  658. *
  659. * @return {Annotation.MockPoint.LabelConfig} the point's label config
  660. */
  661. getLabelConfig: function () {
  662. return {
  663. x: this.x,
  664. y: this.y,
  665. point: this
  666. };
  667. },
  668. /**
  669. * Check if the point is inside its pane.
  670. *
  671. * @return {boolean} A flag indicating whether the point is inside the pane.
  672. */
  673. isInsidePane: function () {
  674. var plotX = this.plotX,
  675. plotY = this.plotY,
  676. xAxis = this.series.xAxis,
  677. yAxis = this.series.yAxis,
  678. isInside = true;
  679. if (xAxis) {
  680. isInside = defined(plotX) && plotX >= 0 && plotX <= xAxis.len;
  681. }
  682. if (yAxis) {
  683. isInside =
  684. isInside &&
  685. defined(plotY) &&
  686. plotY >= 0 && plotY <= yAxis.len;
  687. }
  688. return isInside;
  689. },
  690. /**
  691. * Refresh point values and coordinates based on its options.
  692. */
  693. refresh: function () {
  694. var series = this.series,
  695. xAxis = series.xAxis,
  696. yAxis = series.yAxis,
  697. options = this.getOptions();
  698. if (xAxis) {
  699. this.x = options.x;
  700. this.plotX = xAxis.toPixels(options.x, true);
  701. } else {
  702. this.x = null;
  703. this.plotX = options.x;
  704. }
  705. if (yAxis) {
  706. this.y = options.y;
  707. this.plotY = yAxis.toPixels(options.y, true);
  708. } else {
  709. this.y = null;
  710. this.plotY = options.y;
  711. }
  712. this.isInside = this.isInsidePane();
  713. },
  714. /**
  715. * Translate the point.
  716. *
  717. * @param {number} [cx] origin x transformation
  718. * @param {number} [cy] origin y transformation
  719. * @param {number} dx translation for x coordinate
  720. * @param {number} dy translation for y coordinate
  721. **/
  722. translate: function (cx, cy, dx, dy) {
  723. if (!this.hasDynamicOptions()) {
  724. this.plotX += dx;
  725. this.plotY += dy;
  726. this.refreshOptions();
  727. }
  728. },
  729. /**
  730. * Scale the point.
  731. *
  732. * @param {number} cx origin x transformation
  733. * @param {number} cy origin y transformation
  734. * @param {number} sx scale factor x
  735. * @param {number} sy scale factor y
  736. */
  737. scale: function (cx, cy, sx, sy) {
  738. if (!this.hasDynamicOptions()) {
  739. var x = this.plotX * sx,
  740. y = this.plotY * sy,
  741. tx = (1 - sx) * cx,
  742. ty = (1 - sy) * cy;
  743. this.plotX = tx + x;
  744. this.plotY = ty + y;
  745. this.refreshOptions();
  746. }
  747. },
  748. /**
  749. * Rotate the point.
  750. *
  751. * @param {number} cx origin x rotation
  752. * @param {number} cy origin y rotation
  753. * @param {number} radians
  754. */
  755. rotate: function (cx, cy, radians) {
  756. if (!this.hasDynamicOptions()) {
  757. var cos = Math.cos(radians),
  758. sin = Math.sin(radians),
  759. x = this.plotX,
  760. y = this.plotY,
  761. tx,
  762. ty;
  763. x -= cx;
  764. y -= cy;
  765. tx = x * cos - y * sin;
  766. ty = x * sin + y * cos;
  767. this.plotX = tx + cx;
  768. this.plotY = ty + cy;
  769. this.refreshOptions();
  770. }
  771. },
  772. /**
  773. * Refresh point options based on its plot coordinates.
  774. */
  775. refreshOptions: function () {
  776. var series = this.series,
  777. xAxis = series.xAxis,
  778. yAxis = series.yAxis;
  779. this.x = this.options.x = xAxis ?
  780. this.options.x = xAxis.toValue(this.plotX, true) :
  781. this.plotX;
  782. this.y = this.options.y = yAxis ?
  783. yAxis.toValue(this.plotY, true) :
  784. this.plotY;
  785. }
  786. });
  787. return MockPoint;
  788. });
  789. _registerModule(_modules, 'annotations/controllable/controllableMixin.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['annotations/ControlPoint.js'], _modules['annotations/MockPoint.js']], function (H, U, ControlPoint, MockPoint) {
  790. var isObject = U.isObject,
  791. isString = U.isString,
  792. splat = U.splat;
  793. /**
  794. * It provides methods for handling points, control points
  795. * and points transformations.
  796. *
  797. * @private
  798. * @mixin
  799. * @memberOf Annotation
  800. */
  801. var controllableMixin = {
  802. /**
  803. * Init the controllable
  804. *
  805. * @param {Annotation} annotation - an annotation instance
  806. * @param {Object} options - options specific for controllable
  807. * @param {number} index - index of the controllable element
  808. **/
  809. init: function (annotation, options, index) {
  810. this.annotation = annotation;
  811. this.chart = annotation.chart;
  812. this.options = options;
  813. this.points = [];
  814. this.controlPoints = [];
  815. this.index = index;
  816. this.linkPoints();
  817. this.addControlPoints();
  818. },
  819. /**
  820. * Redirect attr usage on the controllable graphic element.
  821. **/
  822. attr: function () {
  823. this.graphic.attr.apply(this.graphic, arguments);
  824. },
  825. /**
  826. * Get the controllable's points options.
  827. *
  828. * @return {Array<PointLikeOptions>} - an array of points' options.
  829. *
  830. */
  831. getPointsOptions: function () {
  832. var options = this.options;
  833. return options.points || (options.point && splat(options.point));
  834. },
  835. /**
  836. * Utility function for mapping item's options
  837. * to element's attribute
  838. *
  839. * @param {Object} options
  840. * @return {Object} mapped options
  841. **/
  842. attrsFromOptions: function (options) {
  843. var map = this.constructor.attrsMap,
  844. attrs = {},
  845. key,
  846. mappedKey,
  847. styledMode = this.chart.styledMode;
  848. for (key in options) {
  849. mappedKey = map[key];
  850. if (
  851. mappedKey &&
  852. (
  853. !styledMode ||
  854. ['fill', 'stroke', 'stroke-width']
  855. .indexOf(mappedKey) === -1
  856. )
  857. ) {
  858. attrs[mappedKey] = options[key];
  859. }
  860. }
  861. return attrs;
  862. },
  863. /**
  864. * @typedef {Object} Annotation.controllableMixin.Position
  865. * @property {number} x
  866. * @property {number} y
  867. */
  868. /**
  869. * An object which denotes an anchor position
  870. *
  871. * @typedef Annotation.controllableMixin.AnchorPosition
  872. * Annotation.controllableMixin.Position
  873. * @property {number} height
  874. * @property {number} width
  875. */
  876. /**
  877. * An object which denots a controllable's anchor positions
  878. * - relative and absolute.
  879. *
  880. * @typedef {Object} Annotation.controllableMixin.Anchor
  881. * @property {Annotation.controllableMixin.AnchorPosition} relativePosition
  882. * @property {Annotation.controllableMixin.AnchorPosition} absolutePosition
  883. */
  884. /**
  885. * Returns object which denotes anchor position - relative and absolute.
  886. *
  887. * @param {Annotation.PointLike} point a point like object
  888. * @return {Annotation.controllableMixin.Anchor} a controllable anchor
  889. */
  890. anchor: function (point) {
  891. var plotBox = point.series.getPlotBox(),
  892. box = point.mock ?
  893. point.toAnchor() :
  894. H.Tooltip.prototype.getAnchor.call({
  895. chart: point.series.chart
  896. }, point),
  897. anchor = {
  898. x: box[0] + (this.options.x || 0),
  899. y: box[1] + (this.options.y || 0),
  900. height: box[2] || 0,
  901. width: box[3] || 0
  902. };
  903. return {
  904. relativePosition: anchor,
  905. absolutePosition: H.merge(anchor, {
  906. x: anchor.x + plotBox.translateX,
  907. y: anchor.y + plotBox.translateY
  908. })
  909. };
  910. },
  911. /**
  912. * Map point's options to a point-like object.
  913. *
  914. * @param {Highcharts.MockPointOptionsObject} pointOptions
  915. * point's options
  916. * @param {Highcharts.PointLike} point
  917. * a point like instance
  918. *
  919. * @return {Highcharts.PointLike|null}
  920. * if the point is found/set returns this point, otherwise null
  921. */
  922. point: function (pointOptions, point) {
  923. if (pointOptions && pointOptions.series) {
  924. return pointOptions;
  925. }
  926. if (!point || point.series === null) {
  927. if (isObject(pointOptions)) {
  928. point = new MockPoint(
  929. this.chart,
  930. this,
  931. pointOptions
  932. );
  933. } else if (isString(pointOptions)) {
  934. point = this.chart.get(pointOptions) || null;
  935. } else if (typeof pointOptions === 'function') {
  936. var pointConfig = pointOptions.call(point, this);
  937. point = pointConfig.series ?
  938. pointConfig :
  939. new MockPoint(
  940. this.chart,
  941. this,
  942. pointOptions
  943. );
  944. }
  945. }
  946. return point;
  947. },
  948. /**
  949. * Find point-like objects based on points options.
  950. *
  951. * @return {Array<Annotation.PointLike>} an array of point-like objects
  952. */
  953. linkPoints: function () {
  954. var pointsOptions = this.getPointsOptions(),
  955. points = this.points,
  956. len = (pointsOptions && pointsOptions.length) || 0,
  957. i,
  958. point;
  959. for (i = 0; i < len; i++) {
  960. point = this.point(pointsOptions[i], points[i]);
  961. if (!point) {
  962. points.length = 0;
  963. return;
  964. }
  965. if (point.mock) {
  966. point.refresh();
  967. }
  968. points[i] = point;
  969. }
  970. return points;
  971. },
  972. /**
  973. * Add control points to a controllable.
  974. */
  975. addControlPoints: function () {
  976. var controlPointsOptions = this.options.controlPoints;
  977. (controlPointsOptions || []).forEach(
  978. function (controlPointOptions, i) {
  979. var options = H.merge(
  980. this.options.controlPointOptions,
  981. controlPointOptions
  982. );
  983. if (!options.index) {
  984. options.index = i;
  985. }
  986. controlPointsOptions[i] = options;
  987. this.controlPoints.push(
  988. new ControlPoint(this.chart, this, options)
  989. );
  990. },
  991. this
  992. );
  993. },
  994. /**
  995. * Check if a controllable should be rendered/redrawn.
  996. *
  997. * @return {boolean} whether a controllable should be drawn.
  998. */
  999. shouldBeDrawn: function () {
  1000. return Boolean(this.points.length);
  1001. },
  1002. /**
  1003. * Render a controllable.
  1004. **/
  1005. render: function () {
  1006. this.controlPoints.forEach(function (controlPoint) {
  1007. controlPoint.render();
  1008. });
  1009. },
  1010. /**
  1011. * Redraw a controllable.
  1012. *
  1013. * @param {boolean} animation
  1014. **/
  1015. redraw: function (animation) {
  1016. this.controlPoints.forEach(function (controlPoint) {
  1017. controlPoint.redraw(animation);
  1018. });
  1019. },
  1020. /**
  1021. * Transform a controllable with a specific transformation.
  1022. *
  1023. * @param {string} transformation a transformation name
  1024. * @param {number} cx origin x transformation
  1025. * @param {number} cy origin y transformation
  1026. * @param {number} p1 param for the transformation
  1027. * @param {number} p2 param for the transformation
  1028. **/
  1029. transform: function (transformation, cx, cy, p1, p2) {
  1030. if (this.chart.inverted) {
  1031. var temp = cx;
  1032. cx = cy;
  1033. cy = temp;
  1034. }
  1035. this.points.forEach(function (point, i) {
  1036. this.transformPoint(transformation, cx, cy, p1, p2, i);
  1037. }, this);
  1038. },
  1039. /**
  1040. * Transform a point with a specific transformation
  1041. * If a transformed point is a real point it is replaced with
  1042. * the mock point.
  1043. *
  1044. * @param {string} transformation a transformation name
  1045. * @param {number} cx origin x transformation
  1046. * @param {number} cy origin y transformation
  1047. * @param {number} p1 param for the transformation
  1048. * @param {number} p2 param for the transformation
  1049. * @param {number} i index of the point
  1050. *
  1051. **/
  1052. transformPoint: function (transformation, cx, cy, p1, p2, i) {
  1053. var point = this.points[i];
  1054. if (!point.mock) {
  1055. point = this.points[i] = MockPoint.fromPoint(point);
  1056. }
  1057. point[transformation](cx, cy, p1, p2);
  1058. },
  1059. /**
  1060. * Translate a controllable.
  1061. *
  1062. * @param {number} dx translation for x coordinate
  1063. * @param {number} dy translation for y coordinate
  1064. **/
  1065. translate: function (dx, dy) {
  1066. this.transform('translate', null, null, dx, dy);
  1067. },
  1068. /**
  1069. * Translate a specific point within a controllable.
  1070. *
  1071. * @param {number} dx translation for x coordinate
  1072. * @param {number} dy translation for y coordinate
  1073. * @param {number} i index of the point
  1074. **/
  1075. translatePoint: function (dx, dy, i) {
  1076. this.transformPoint('translate', null, null, dx, dy, i);
  1077. },
  1078. /**
  1079. * Translate shape within controllable item.
  1080. * Replaces `controllable.translate` method.
  1081. *
  1082. * @param {number} dx translation for x coordinate
  1083. * @param {number} dy translation for y coordinate
  1084. */
  1085. translateShape: function (dx, dy) {
  1086. var chart = this.annotation.chart,
  1087. // Annotation.options
  1088. shapeOptions = this.annotation.userOptions,
  1089. // Chart.options.annotations
  1090. annotationIndex = chart.annotations.indexOf(this.annotation),
  1091. chartOptions = chart.options.annotations[annotationIndex];
  1092. this.translatePoint(dx, dy, 0);
  1093. // Options stored in:
  1094. // - chart (for exporting)
  1095. // - current config (for redraws)
  1096. chartOptions[this.collection][this.index].point = this.options.point;
  1097. shapeOptions[this.collection][this.index].point = this.options.point;
  1098. },
  1099. /**
  1100. * Rotate a controllable.
  1101. *
  1102. * @param {number} cx origin x rotation
  1103. * @param {number} cy origin y rotation
  1104. * @param {number} radians
  1105. **/
  1106. rotate: function (cx, cy, radians) {
  1107. this.transform('rotate', cx, cy, radians);
  1108. },
  1109. /**
  1110. * Scale a controllable.
  1111. *
  1112. * @param {number} cx origin x rotation
  1113. * @param {number} cy origin y rotation
  1114. * @param {number} sx scale factor x
  1115. * @param {number} sy scale factor y
  1116. */
  1117. scale: function (cx, cy, sx, sy) {
  1118. this.transform('scale', cx, cy, sx, sy);
  1119. },
  1120. /**
  1121. * Set control points' visibility.
  1122. *
  1123. * @param {boolean} [visible]
  1124. */
  1125. setControlPointsVisibility: function (visible) {
  1126. this.controlPoints.forEach(function (controlPoint) {
  1127. controlPoint.setVisibility(visible);
  1128. });
  1129. },
  1130. /**
  1131. * Destroy a controllable.
  1132. */
  1133. destroy: function () {
  1134. if (this.graphic) {
  1135. this.graphic = this.graphic.destroy();
  1136. }
  1137. if (this.tracker) {
  1138. this.tracker = this.tracker.destroy();
  1139. }
  1140. this.controlPoints.forEach(function (controlPoint) {
  1141. controlPoint.destroy();
  1142. });
  1143. this.chart = null;
  1144. this.points = null;
  1145. this.controlPoints = null;
  1146. this.options = null;
  1147. if (this.annotation) {
  1148. this.annotation = null;
  1149. }
  1150. },
  1151. /**
  1152. * Update a controllable.
  1153. *
  1154. * @param {Object} newOptions
  1155. */
  1156. update: function (newOptions) {
  1157. var annotation = this.annotation,
  1158. options = H.merge(true, this.options, newOptions),
  1159. parentGroup = this.graphic.parentGroup;
  1160. this.destroy();
  1161. this.constructor(annotation, options);
  1162. this.render(parentGroup);
  1163. this.redraw();
  1164. }
  1165. };
  1166. return controllableMixin;
  1167. });
  1168. _registerModule(_modules, 'annotations/controllable/markerMixin.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
  1169. var defined = U.defined,
  1170. objectEach = U.objectEach,
  1171. splat = U.splat;
  1172. /**
  1173. * Options for configuring markers for annotations.
  1174. *
  1175. * An example of the arrow marker:
  1176. * <pre>
  1177. * {
  1178. * arrow: {
  1179. * id: 'arrow',
  1180. * tagName: 'marker',
  1181. * refY: 5,
  1182. * refX: 5,
  1183. * markerWidth: 10,
  1184. * markerHeight: 10,
  1185. * children: [{
  1186. * tagName: 'path',
  1187. * attrs: {
  1188. * d: 'M 0 0 L 10 5 L 0 10 Z',
  1189. * strokeWidth: 0
  1190. * }
  1191. * }]
  1192. * }
  1193. * }
  1194. * </pre>
  1195. * @type {Object}
  1196. * @sample highcharts/annotations/custom-markers/
  1197. * Define a custom marker for annotations
  1198. * @sample highcharts/css/annotations-markers/
  1199. * Define markers in a styled mode
  1200. * @since 6.0.0
  1201. * @apioption defs
  1202. */
  1203. var defaultMarkers = {
  1204. arrow: {
  1205. tagName: 'marker',
  1206. render: false,
  1207. id: 'arrow',
  1208. refY: 5,
  1209. refX: 9,
  1210. markerWidth: 10,
  1211. markerHeight: 10,
  1212. children: [{
  1213. tagName: 'path',
  1214. d: 'M 0 0 L 10 5 L 0 10 Z', // triangle (used as an arrow)
  1215. strokeWidth: 0
  1216. }]
  1217. },
  1218. 'reverse-arrow': {
  1219. tagName: 'marker',
  1220. render: false,
  1221. id: 'reverse-arrow',
  1222. refY: 5,
  1223. refX: 1,
  1224. markerWidth: 10,
  1225. markerHeight: 10,
  1226. children: [{
  1227. tagName: 'path',
  1228. // reverse triangle (used as an arrow)
  1229. d: 'M 0 5 L 10 0 L 10 10 Z',
  1230. strokeWidth: 0
  1231. }]
  1232. }
  1233. };
  1234. H.SVGRenderer.prototype.addMarker = function (id, markerOptions) {
  1235. var options = { id: id };
  1236. var attrs = {
  1237. stroke: markerOptions.color || 'none',
  1238. fill: markerOptions.color || 'rgba(0, 0, 0, 0.75)'
  1239. };
  1240. options.children = markerOptions.children.map(function (child) {
  1241. return H.merge(attrs, child);
  1242. });
  1243. var marker = this.definition(H.merge(true, {
  1244. markerWidth: 20,
  1245. markerHeight: 20,
  1246. refX: 0,
  1247. refY: 0,
  1248. orient: 'auto'
  1249. }, markerOptions, options));
  1250. marker.id = id;
  1251. return marker;
  1252. };
  1253. var createMarkerSetter = function (markerType) {
  1254. return function (value) {
  1255. this.attr(markerType, 'url(#' + value + ')');
  1256. };
  1257. };
  1258. /**
  1259. * @private
  1260. * @mixin
  1261. */
  1262. var markerMixin = {
  1263. markerEndSetter: createMarkerSetter('marker-end'),
  1264. markerStartSetter: createMarkerSetter('marker-start'),
  1265. /*
  1266. * Set markers.
  1267. *
  1268. * @param {Controllable} item
  1269. */
  1270. setItemMarkers: function (item) {
  1271. var itemOptions = item.options,
  1272. chart = item.chart,
  1273. defs = chart.options.defs,
  1274. fill = itemOptions.fill,
  1275. color = defined(fill) && fill !== 'none' ?
  1276. fill :
  1277. itemOptions.stroke,
  1278. setMarker = function (markerType) {
  1279. var markerId = itemOptions[markerType],
  1280. def,
  1281. predefinedMarker,
  1282. key,
  1283. marker;
  1284. if (markerId) {
  1285. for (key in defs) {
  1286. def = defs[key];
  1287. if (
  1288. markerId === def.id && def.tagName === 'marker'
  1289. ) {
  1290. predefinedMarker = def;
  1291. break;
  1292. }
  1293. }
  1294. if (predefinedMarker) {
  1295. marker = item[markerType] = chart.renderer
  1296. .addMarker(
  1297. (itemOptions.id || H.uniqueKey()) + '-' +
  1298. predefinedMarker.id,
  1299. H.merge(predefinedMarker, { color: color })
  1300. );
  1301. item.attr(markerType, marker.attr('id'));
  1302. }
  1303. }
  1304. };
  1305. ['markerStart', 'markerEnd'].forEach(setMarker);
  1306. }
  1307. };
  1308. // In a styled mode definition is implemented
  1309. H.SVGRenderer.prototype.definition = function (def) {
  1310. var ren = this;
  1311. function recurse(config, parent) {
  1312. var ret;
  1313. splat(config).forEach(function (item) {
  1314. var node = ren.createElement(item.tagName),
  1315. attr = {};
  1316. // Set attributes
  1317. objectEach(item, function (val, key) {
  1318. if (
  1319. key !== 'tagName' &&
  1320. key !== 'children' &&
  1321. key !== 'textContent'
  1322. ) {
  1323. attr[key] = val;
  1324. }
  1325. });
  1326. node.attr(attr);
  1327. // Add to the tree
  1328. node.add(parent || ren.defs);
  1329. // Add text content
  1330. if (item.textContent) {
  1331. node.element.appendChild(
  1332. H.doc.createTextNode(item.textContent)
  1333. );
  1334. }
  1335. // Recurse
  1336. recurse(item.children || [], node);
  1337. ret = node;
  1338. });
  1339. // Return last node added (on top level it's the only one)
  1340. return ret;
  1341. }
  1342. return recurse(def);
  1343. };
  1344. H.addEvent(H.Chart, 'afterGetContainer', function () {
  1345. this.options.defs = H.merge(defaultMarkers, this.options.defs || {});
  1346. objectEach(this.options.defs, function (def) {
  1347. if (def.tagName === 'marker' && def.render !== false) {
  1348. this.renderer.addMarker(def.id, def);
  1349. }
  1350. }, this);
  1351. });
  1352. return markerMixin;
  1353. });
  1354. _registerModule(_modules, 'annotations/controllable/ControllablePath.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['annotations/controllable/controllableMixin.js'], _modules['annotations/controllable/markerMixin.js']], function (H, U, controllableMixin, markerMixin) {
  1355. var extend = U.extend;
  1356. // See TRACKER_FILL in highcharts.src.js
  1357. var TRACKER_FILL = 'rgba(192,192,192,' + (H.svg ? 0.0001 : 0.002) + ')';
  1358. /**
  1359. * A controllable path class.
  1360. *
  1361. * @class
  1362. * @mixes Annotation.controllableMixin
  1363. * @mixes Annotation.markerMixin
  1364. * @memberOf Annotation
  1365. *
  1366. * @param {Highcharts.Annotation}
  1367. * @param {Object} options a path's options object
  1368. * @param {number} index of the path
  1369. **/
  1370. function ControllablePath(annotation, options, index) {
  1371. this.init(annotation, options, index);
  1372. this.collection = 'shapes';
  1373. }
  1374. /**
  1375. * @typedef {Object} Annotation.ControllablePath.AttrsMap
  1376. * @property {string} dashStyle=dashstyle
  1377. * @property {string} strokeWidth=stroke-width
  1378. * @property {string} stroke=stroke
  1379. * @property {string} fill=fill
  1380. * @property {string} zIndex=zIndex
  1381. */
  1382. /**
  1383. * A map object which allows to map options attributes to element attributes
  1384. *
  1385. * @type {Annotation.ControllablePath.AttrsMap}
  1386. */
  1387. ControllablePath.attrsMap = {
  1388. dashStyle: 'dashstyle',
  1389. strokeWidth: 'stroke-width',
  1390. stroke: 'stroke',
  1391. fill: 'fill',
  1392. zIndex: 'zIndex'
  1393. };
  1394. H.merge(
  1395. true,
  1396. ControllablePath.prototype,
  1397. controllableMixin, /** @lends Annotation.ControllablePath# */ {
  1398. /**
  1399. * @type 'path'
  1400. */
  1401. type: 'path',
  1402. setMarkers: markerMixin.setItemMarkers,
  1403. /**
  1404. * Map the controllable path to 'd' path attribute
  1405. *
  1406. * @return {Array<(string|number)>} a path's d attribute
  1407. */
  1408. toD: function () {
  1409. var d = this.options.d;
  1410. if (d) {
  1411. return typeof d === 'function' ?
  1412. d.call(this) :
  1413. d;
  1414. }
  1415. var points = this.points,
  1416. len = points.length,
  1417. showPath = len,
  1418. point = points[0],
  1419. position = showPath && this.anchor(point).absolutePosition,
  1420. pointIndex = 0,
  1421. dIndex = 2,
  1422. command;
  1423. d = position && ['M', position.x, position.y];
  1424. while (++pointIndex < len && showPath) {
  1425. point = points[pointIndex];
  1426. command = point.command || 'L';
  1427. position = this.anchor(point).absolutePosition;
  1428. if (command === 'Z') {
  1429. d[++dIndex] = command;
  1430. } else {
  1431. if (command !== points[pointIndex - 1].command) {
  1432. d[++dIndex] = command;
  1433. }
  1434. d[++dIndex] = position.x;
  1435. d[++dIndex] = position.y;
  1436. }
  1437. showPath = point.series.visible;
  1438. }
  1439. return showPath ?
  1440. this.chart.renderer.crispLine(d, this.graphic.strokeWidth()) :
  1441. null;
  1442. },
  1443. shouldBeDrawn: function () {
  1444. return controllableMixin.shouldBeDrawn.call(this) ||
  1445. Boolean(this.options.d);
  1446. },
  1447. render: function (parent) {
  1448. var options = this.options,
  1449. attrs = this.attrsFromOptions(options);
  1450. this.graphic = this.annotation.chart.renderer
  1451. .path(['M', 0, 0])
  1452. .attr(attrs)
  1453. .add(parent);
  1454. if (options.className) {
  1455. this.graphic.addClass(options.className);
  1456. }
  1457. this.tracker = this.annotation.chart.renderer
  1458. .path(['M', 0, 0])
  1459. .addClass('highcharts-tracker-line')
  1460. .attr({
  1461. zIndex: 2
  1462. })
  1463. .add(parent);
  1464. if (!this.annotation.chart.styledMode) {
  1465. this.tracker.attr({
  1466. 'stroke-linejoin': 'round', // #1225
  1467. stroke: TRACKER_FILL,
  1468. fill: TRACKER_FILL,
  1469. 'stroke-width': this.graphic.strokeWidth() +
  1470. options.snap * 2
  1471. });
  1472. }
  1473. controllableMixin.render.call(this);
  1474. extend(this.graphic, {
  1475. markerStartSetter: markerMixin.markerStartSetter,
  1476. markerEndSetter: markerMixin.markerEndSetter
  1477. });
  1478. this.setMarkers(this);
  1479. },
  1480. redraw: function (animation) {
  1481. var d = this.toD(),
  1482. action = animation ? 'animate' : 'attr';
  1483. if (d) {
  1484. this.graphic[action]({ d: d });
  1485. this.tracker[action]({ d: d });
  1486. } else {
  1487. this.graphic.attr({ d: 'M 0 ' + -9e9 });
  1488. this.tracker.attr({ d: 'M 0 ' + -9e9 });
  1489. }
  1490. this.graphic.placed = this.tracker.placed = Boolean(d);
  1491. controllableMixin.redraw.call(this, animation);
  1492. }
  1493. }
  1494. );
  1495. return ControllablePath;
  1496. });
  1497. _registerModule(_modules, 'annotations/controllable/ControllableRect.js', [_modules['parts/Globals.js'], _modules['annotations/controllable/controllableMixin.js'], _modules['annotations/controllable/ControllablePath.js']], function (H, controllableMixin, ControllablePath) {
  1498. /**
  1499. * A controllable rect class.
  1500. *
  1501. * @class
  1502. * @mixes Annotation.controllableMixin
  1503. * @memberOf Annotation
  1504. *
  1505. * @param {Highcharts.Annotation} annotation an annotation instance
  1506. * @param {Object} options a rect's options
  1507. * @param {number} index of the rectangle
  1508. **/
  1509. function ControllableRect(annotation, options, index) {
  1510. this.init(annotation, options, index);
  1511. this.collection = 'shapes';
  1512. }
  1513. /**
  1514. * @typedef {Annotation.ControllablePath.AttrsMap}
  1515. * Annotation.ControllableRect.AttrsMap
  1516. * @property {string} width=width
  1517. * @property {string} height=height
  1518. */
  1519. /**
  1520. * A map object which allows to map options attributes to element attributes
  1521. *
  1522. * @type {Annotation.ControllableRect.AttrsMap}
  1523. */
  1524. ControllableRect.attrsMap = H.merge(ControllablePath.attrsMap, {
  1525. width: 'width',
  1526. height: 'height'
  1527. });
  1528. H.merge(
  1529. true,
  1530. ControllableRect.prototype,
  1531. controllableMixin, /** @lends Annotation.ControllableRect# */ {
  1532. /**
  1533. * @type 'rect'
  1534. */
  1535. type: 'rect',
  1536. translate: controllableMixin.translateShape,
  1537. render: function (parent) {
  1538. var attrs = this.attrsFromOptions(this.options);
  1539. this.graphic = this.annotation.chart.renderer
  1540. .rect(0, -9e9, 0, 0)
  1541. .attr(attrs)
  1542. .add(parent);
  1543. controllableMixin.render.call(this);
  1544. },
  1545. redraw: function (animation) {
  1546. var position = this.anchor(this.points[0]).absolutePosition;
  1547. if (position) {
  1548. this.graphic[animation ? 'animate' : 'attr']({
  1549. x: position.x,
  1550. y: position.y,
  1551. width: this.options.width,
  1552. height: this.options.height
  1553. });
  1554. } else {
  1555. this.attr({
  1556. x: 0,
  1557. y: -9e9
  1558. });
  1559. }
  1560. this.graphic.placed = Boolean(position);
  1561. controllableMixin.redraw.call(this, animation);
  1562. }
  1563. }
  1564. );
  1565. return ControllableRect;
  1566. });
  1567. _registerModule(_modules, 'annotations/controllable/ControllableCircle.js', [_modules['parts/Globals.js'], _modules['annotations/controllable/controllableMixin.js'], _modules['annotations/controllable/ControllablePath.js']], function (H, controllableMixin, ControllablePath) {
  1568. /**
  1569. * A controllable circle class.
  1570. *
  1571. * @constructor
  1572. * @mixes Annotation.controllableMixin
  1573. * @memberOf Annotation
  1574. *
  1575. * @param {Highcharts.Annotation} annotation an annotation instance
  1576. * @param {Object} options a shape's options
  1577. * @param {number} index of the circle
  1578. **/
  1579. function ControllableCircle(annotation, options, index) {
  1580. this.init(annotation, options, index);
  1581. this.collection = 'shapes';
  1582. }
  1583. /**
  1584. * A map object which allows to map options attributes to element attributes.
  1585. */
  1586. ControllableCircle.attrsMap = H.merge(ControllablePath.attrsMap, {
  1587. r: 'r'
  1588. });
  1589. H.merge(
  1590. true,
  1591. ControllableCircle.prototype,
  1592. controllableMixin, /** @lends Annotation.ControllableCircle# */ {
  1593. /**
  1594. * @type 'circle'
  1595. */
  1596. type: 'circle',
  1597. translate: controllableMixin.translateShape,
  1598. render: function (parent) {
  1599. var attrs = this.attrsFromOptions(this.options);
  1600. this.graphic = this.annotation.chart.renderer
  1601. .circle(0, -9e9, 0)
  1602. .attr(attrs)
  1603. .add(parent);
  1604. controllableMixin.render.call(this);
  1605. },
  1606. redraw: function (animation) {
  1607. var position = this.anchor(this.points[0]).absolutePosition;
  1608. if (position) {
  1609. this.graphic[animation ? 'animate' : 'attr']({
  1610. x: position.x,
  1611. y: position.y,
  1612. r: this.options.r
  1613. });
  1614. } else {
  1615. this.graphic.attr({
  1616. x: 0,
  1617. y: -9e9
  1618. });
  1619. }
  1620. this.graphic.placed = Boolean(position);
  1621. controllableMixin.redraw.call(this, animation);
  1622. },
  1623. /**
  1624. * Set the radius.
  1625. *
  1626. * @param {number} r a radius to be set
  1627. */
  1628. setRadius: function (r) {
  1629. this.options.r = r;
  1630. }
  1631. }
  1632. );
  1633. return ControllableCircle;
  1634. });
  1635. _registerModule(_modules, 'annotations/controllable/ControllableLabel.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['annotations/controllable/controllableMixin.js'], _modules['annotations/MockPoint.js']], function (H, U, controllableMixin, MockPoint) {
  1636. var extend = U.extend,
  1637. isNumber = U.isNumber,
  1638. pick = U.pick;
  1639. /**
  1640. * @private
  1641. * @interface Highcharts.AnnotationAnchorObject
  1642. *//**
  1643. * Relative to the plot area position
  1644. * @name Highcharts.AnnotationAnchorObject#relativePosition
  1645. * @type {Highcharts.AnnotationAnchorPositionObject}
  1646. *//**
  1647. * Absolute position
  1648. * @name Highcharts.AnnotationAnchorObject#absolutePosition
  1649. * @type {Highcharts.AnnotationAnchorPositionObject}
  1650. */
  1651. /**
  1652. * An object which denotes an anchor position
  1653. *
  1654. * @private
  1655. * @interface Highcharts.AnnotationAnchorPositionObject
  1656. *//**
  1657. * @name Highcharts.AnnotationAnchorPositionObject#x
  1658. * @property {number}
  1659. *//**
  1660. * @name Highcharts.AnnotationAnchorPositionObject#y
  1661. * @property {number}
  1662. *//**
  1663. * @name Highcharts.AnnotationAnchorPositionObject#height
  1664. * @property {number}
  1665. *//**
  1666. * @name Highcharts.AnnotationAnchorPositionObject#width
  1667. * @property {number}
  1668. */
  1669. /**
  1670. * A controllable label class.
  1671. *
  1672. * @private
  1673. * @class
  1674. * @name Annotation.ControllableLabel
  1675. *
  1676. * @mixes Annotation.controllableMixin
  1677. *
  1678. * @param {Highcharts.Annotation} annotation an annotation instance
  1679. * @param {object} options a label's options
  1680. * @param {number} index of the label
  1681. **/
  1682. function ControllableLabel(annotation, options, index) {
  1683. this.init(annotation, options, index);
  1684. this.collection = 'labels';
  1685. }
  1686. /**
  1687. * Shapes which do not have background - the object is used for proper
  1688. * setting of the contrast color.
  1689. *
  1690. * @type {Array<string>}
  1691. */
  1692. ControllableLabel.shapesWithoutBackground = ['connector'];
  1693. /**
  1694. * Returns new aligned position based alignment options and box to align to.
  1695. * It is almost a one-to-one copy from SVGElement.prototype.align
  1696. * except it does not use and mutate an element
  1697. *
  1698. * @param {Object} alignOptions
  1699. * @param {Object} box
  1700. * @return {Annotation.controllableMixin.Position} aligned position
  1701. */
  1702. ControllableLabel.alignedPosition = function (alignOptions, box) {
  1703. var align = alignOptions.align,
  1704. vAlign = alignOptions.verticalAlign,
  1705. x = (box.x || 0) + (alignOptions.x || 0),
  1706. y = (box.y || 0) + (alignOptions.y || 0),
  1707. alignFactor,
  1708. vAlignFactor;
  1709. if (align === 'right') {
  1710. alignFactor = 1;
  1711. } else if (align === 'center') {
  1712. alignFactor = 2;
  1713. }
  1714. if (alignFactor) {
  1715. x += (box.width - (alignOptions.width || 0)) / alignFactor;
  1716. }
  1717. if (vAlign === 'bottom') {
  1718. vAlignFactor = 1;
  1719. } else if (vAlign === 'middle') {
  1720. vAlignFactor = 2;
  1721. }
  1722. if (vAlignFactor) {
  1723. y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
  1724. }
  1725. return {
  1726. x: Math.round(x),
  1727. y: Math.round(y)
  1728. };
  1729. };
  1730. /**
  1731. * Returns new alignment options for a label if the label is outside the
  1732. * plot area. It is almost a one-to-one copy from
  1733. * Series.prototype.justifyDataLabel except it does not mutate the label and
  1734. * it works with absolute instead of relative position.
  1735. *
  1736. * @param {Object} label
  1737. * @param {Object} alignOptions
  1738. * @param {Object} alignAttr
  1739. * @return {Object} justified options
  1740. **/
  1741. ControllableLabel.justifiedOptions = function (
  1742. chart,
  1743. label,
  1744. alignOptions,
  1745. alignAttr
  1746. ) {
  1747. var align = alignOptions.align,
  1748. verticalAlign = alignOptions.verticalAlign,
  1749. padding = label.box ? 0 : (label.padding || 0),
  1750. bBox = label.getBBox(),
  1751. off,
  1752. options = {
  1753. align: align,
  1754. verticalAlign: verticalAlign,
  1755. x: alignOptions.x,
  1756. y: alignOptions.y,
  1757. width: label.width,
  1758. height: label.height
  1759. },
  1760. x = alignAttr.x - chart.plotLeft,
  1761. y = alignAttr.y - chart.plotTop;
  1762. // Off left
  1763. off = x + padding;
  1764. if (off < 0) {
  1765. if (align === 'right') {
  1766. options.align = 'left';
  1767. } else {
  1768. options.x = -off;
  1769. }
  1770. }
  1771. // Off right
  1772. off = x + bBox.width - padding;
  1773. if (off > chart.plotWidth) {
  1774. if (align === 'left') {
  1775. options.align = 'right';
  1776. } else {
  1777. options.x = chart.plotWidth - off;
  1778. }
  1779. }
  1780. // Off top
  1781. off = y + padding;
  1782. if (off < 0) {
  1783. if (verticalAlign === 'bottom') {
  1784. options.verticalAlign = 'top';
  1785. } else {
  1786. options.y = -off;
  1787. }
  1788. }
  1789. // Off bottom
  1790. off = y + bBox.height - padding;
  1791. if (off > chart.plotHeight) {
  1792. if (verticalAlign === 'top') {
  1793. options.verticalAlign = 'bottom';
  1794. } else {
  1795. options.y = chart.plotHeight - off;
  1796. }
  1797. }
  1798. return options;
  1799. };
  1800. /**
  1801. * @typedef {Object} Annotation.ControllableLabel.AttrsMap
  1802. * @property {string} backgroundColor=fill
  1803. * @property {string} borderColor=stroke
  1804. * @property {string} borderWidth=stroke-width
  1805. * @property {string} zIndex=zIndex
  1806. * @property {string} borderRadius=r
  1807. * @property {string} padding=padding
  1808. */
  1809. /**
  1810. * A map object which allows to map options attributes to element attributes
  1811. *
  1812. * @type {Annotation.ControllableLabel.AttrsMap}
  1813. */
  1814. ControllableLabel.attrsMap = {
  1815. backgroundColor: 'fill',
  1816. borderColor: 'stroke',
  1817. borderWidth: 'stroke-width',
  1818. zIndex: 'zIndex',
  1819. borderRadius: 'r',
  1820. padding: 'padding'
  1821. };
  1822. H.merge(
  1823. true,
  1824. ControllableLabel.prototype,
  1825. controllableMixin, /** @lends Annotation.ControllableLabel# */ {
  1826. /**
  1827. * Translate the point of the label by deltaX and deltaY translations.
  1828. * The point is the label's anchor.
  1829. *
  1830. * @param {number} dx translation for x coordinate
  1831. * @param {number} dy translation for y coordinate
  1832. **/
  1833. translatePoint: function (dx, dy) {
  1834. controllableMixin.translatePoint.call(this, dx, dy, 0);
  1835. },
  1836. /**
  1837. * Translate x and y position relative to the label's anchor.
  1838. *
  1839. * @param {number} dx translation for x coordinate
  1840. * @param {number} dy translation for y coordinate
  1841. **/
  1842. translate: function (dx, dy) {
  1843. var chart = this.annotation.chart,
  1844. // Annotation.options
  1845. labelOptions = this.annotation.userOptions,
  1846. // Chart.options.annotations
  1847. annotationIndex = chart.annotations.indexOf(this.annotation),
  1848. chartAnnotations = chart.options.annotations,
  1849. chartOptions = chartAnnotations[annotationIndex],
  1850. temp;
  1851. if (chart.inverted) {
  1852. temp = dx;
  1853. dx = dy;
  1854. dy = temp;
  1855. }
  1856. // Local options:
  1857. this.options.x += dx;
  1858. this.options.y += dy;
  1859. // Options stored in chart:
  1860. chartOptions[this.collection][this.index].x = this.options.x;
  1861. chartOptions[this.collection][this.index].y = this.options.y;
  1862. labelOptions[this.collection][this.index].x = this.options.x;
  1863. labelOptions[this.collection][this.index].y = this.options.y;
  1864. },
  1865. render: function (parent) {
  1866. var options = this.options,
  1867. attrs = this.attrsFromOptions(options),
  1868. style = options.style;
  1869. this.graphic = this.annotation.chart.renderer
  1870. .label(
  1871. '',
  1872. 0,
  1873. -9999, // #10055
  1874. options.shape,
  1875. null,
  1876. null,
  1877. options.useHTML,
  1878. null,
  1879. 'annotation-label'
  1880. )
  1881. .attr(attrs)
  1882. .add(parent);
  1883. if (!this.annotation.chart.styledMode) {
  1884. if (style.color === 'contrast') {
  1885. style.color = this.annotation.chart.renderer.getContrast(
  1886. ControllableLabel.shapesWithoutBackground.indexOf(
  1887. options.shape
  1888. ) > -1 ? '#FFFFFF' : options.backgroundColor
  1889. );
  1890. }
  1891. this.graphic
  1892. .css(options.style)
  1893. .shadow(options.shadow);
  1894. }
  1895. if (options.className) {
  1896. this.graphic.addClass(options.className);
  1897. }
  1898. this.graphic.labelrank = options.labelrank;
  1899. controllableMixin.render.call(this);
  1900. },
  1901. redraw: function (animation) {
  1902. var options = this.options,
  1903. text = this.text || options.format || options.text,
  1904. label = this.graphic,
  1905. point = this.points[0],
  1906. show = false,
  1907. anchor,
  1908. attrs;
  1909. label.attr({
  1910. text: text ?
  1911. H.format(
  1912. text,
  1913. point.getLabelConfig(),
  1914. this.annotation.chart
  1915. ) :
  1916. options.formatter.call(point, this)
  1917. });
  1918. anchor = this.anchor(point);
  1919. attrs = this.position(anchor);
  1920. show = attrs;
  1921. if (show) {
  1922. label.alignAttr = attrs;
  1923. attrs.anchorX = anchor.absolutePosition.x;
  1924. attrs.anchorY = anchor.absolutePosition.y;
  1925. label[animation ? 'animate' : 'attr'](attrs);
  1926. } else {
  1927. label.attr({
  1928. x: 0,
  1929. y: -9999 // #10055
  1930. });
  1931. }
  1932. label.placed = Boolean(show);
  1933. controllableMixin.redraw.call(this, animation);
  1934. },
  1935. /**
  1936. * All basic shapes don't support alignTo() method except label.
  1937. * For a controllable label, we need to subtract translation from
  1938. * options.
  1939. */
  1940. anchor: function () {
  1941. var anchor = controllableMixin.anchor.apply(this, arguments),
  1942. x = this.options.x || 0,
  1943. y = this.options.y || 0;
  1944. anchor.absolutePosition.x -= x;
  1945. anchor.absolutePosition.y -= y;
  1946. anchor.relativePosition.x -= x;
  1947. anchor.relativePosition.y -= y;
  1948. return anchor;
  1949. },
  1950. /**
  1951. * Returns the label position relative to its anchor.
  1952. *
  1953. * @param {Highcharts.AnnotationAnchorObject} anchor
  1954. *
  1955. * @return {Highcharts.AnnotationAnchorPositionObject|null} position
  1956. */
  1957. position: function (anchor) {
  1958. var item = this.graphic,
  1959. chart = this.annotation.chart,
  1960. point = this.points[0],
  1961. itemOptions = this.options,
  1962. anchorAbsolutePosition = anchor.absolutePosition,
  1963. anchorRelativePosition = anchor.relativePosition,
  1964. itemPosition,
  1965. alignTo,
  1966. itemPosRelativeX,
  1967. itemPosRelativeY,
  1968. showItem =
  1969. point.series.visible &&
  1970. MockPoint.prototype.isInsidePane.call(point);
  1971. if (showItem) {
  1972. if (itemOptions.distance) {
  1973. itemPosition = H.Tooltip.prototype.getPosition.call(
  1974. {
  1975. chart: chart,
  1976. distance: pick(itemOptions.distance, 16)
  1977. },
  1978. item.width,
  1979. item.height,
  1980. {
  1981. plotX: anchorRelativePosition.x,
  1982. plotY: anchorRelativePosition.y,
  1983. negative: point.negative,
  1984. ttBelow: point.ttBelow,
  1985. h: anchorRelativePosition.height ||
  1986. anchorRelativePosition.width
  1987. }
  1988. );
  1989. } else if (itemOptions.positioner) {
  1990. itemPosition = itemOptions.positioner.call(this);
  1991. } else {
  1992. alignTo = {
  1993. x: anchorAbsolutePosition.x,
  1994. y: anchorAbsolutePosition.y,
  1995. width: 0,
  1996. height: 0
  1997. };
  1998. itemPosition = ControllableLabel.alignedPosition(
  1999. extend(itemOptions, {
  2000. width: item.width,
  2001. height: item.height
  2002. }),
  2003. alignTo
  2004. );
  2005. if (this.options.overflow === 'justify') {
  2006. itemPosition = ControllableLabel.alignedPosition(
  2007. ControllableLabel.justifiedOptions(
  2008. chart,
  2009. item,
  2010. itemOptions,
  2011. itemPosition
  2012. ),
  2013. alignTo
  2014. );
  2015. }
  2016. }
  2017. if (itemOptions.crop) {
  2018. itemPosRelativeX = itemPosition.x - chart.plotLeft;
  2019. itemPosRelativeY = itemPosition.y - chart.plotTop;
  2020. showItem =
  2021. chart.isInsidePlot(
  2022. itemPosRelativeX,
  2023. itemPosRelativeY
  2024. ) &&
  2025. chart.isInsidePlot(
  2026. itemPosRelativeX + item.width,
  2027. itemPosRelativeY + item.height
  2028. );
  2029. }
  2030. }
  2031. return showItem ? itemPosition : null;
  2032. }
  2033. }
  2034. );
  2035. /* ********************************************************************** */
  2036. /**
  2037. * General symbol definition for labels with connector
  2038. * @private
  2039. */
  2040. H.SVGRenderer.prototype.symbols.connector = function (x, y, w, h, options) {
  2041. var anchorX = options && options.anchorX,
  2042. anchorY = options && options.anchorY,
  2043. path,
  2044. yOffset,
  2045. lateral = w / 2;
  2046. if (isNumber(anchorX) && isNumber(anchorY)) {
  2047. path = ['M', anchorX, anchorY];
  2048. // Prefer 45 deg connectors
  2049. yOffset = y - anchorY;
  2050. if (yOffset < 0) {
  2051. yOffset = -h - yOffset;
  2052. }
  2053. if (yOffset < w) {
  2054. lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;
  2055. }
  2056. // Anchor below label
  2057. if (anchorY > y + h) {
  2058. path.push('L', x + lateral, y + h);
  2059. // Anchor above label
  2060. } else if (anchorY < y) {
  2061. path.push('L', x + lateral, y);
  2062. // Anchor left of label
  2063. } else if (anchorX < x) {
  2064. path.push('L', x, y + h / 2);
  2065. // Anchor right of label
  2066. } else if (anchorX > x + w) {
  2067. path.push('L', x + w, y + h / 2);
  2068. }
  2069. }
  2070. return path || [];
  2071. };
  2072. return ControllableLabel;
  2073. });
  2074. _registerModule(_modules, 'annotations/controllable/ControllableImage.js', [_modules['parts/Globals.js'], _modules['annotations/controllable/controllableMixin.js'], _modules['annotations/controllable/ControllableLabel.js']], function (H, controllableMixin, ControllableLabel) {
  2075. /**
  2076. * A controllable image class.
  2077. *
  2078. * @class
  2079. * @mixes Annotation.controllableMixin
  2080. * @memberOf Annotation
  2081. *
  2082. * @param {Highcharts.Annotation} annotation - an annotation instance
  2083. * @param {Object} options a controllable's options
  2084. * @param {number} index of the image
  2085. **/
  2086. function ControllableImage(annotation, options, index) {
  2087. this.init(annotation, options, index);
  2088. this.collection = 'shapes';
  2089. }
  2090. /**
  2091. * @typedef {Object} Annotation.ControllableImage.AttrsMap
  2092. * @property {string} width=width
  2093. * @property {string} height=height
  2094. * @property {string} zIndex=zIndex
  2095. */
  2096. /**
  2097. * A map object which allows to map options attributes to element attributes
  2098. *
  2099. * @type {Annotation.ControllableImage.AttrsMap}
  2100. */
  2101. ControllableImage.attrsMap = {
  2102. width: 'width',
  2103. height: 'height',
  2104. zIndex: 'zIndex'
  2105. };
  2106. H.merge(
  2107. true,
  2108. ControllableImage.prototype,
  2109. controllableMixin, /** @lends Annotation.ControllableImage# */ {
  2110. /**
  2111. * @type 'image'
  2112. */
  2113. type: 'image',
  2114. translate: controllableMixin.translateShape,
  2115. render: function (parent) {
  2116. var attrs = this.attrsFromOptions(this.options),
  2117. options = this.options;
  2118. this.graphic = this.annotation.chart.renderer
  2119. .image(options.src, 0, -9e9, options.width, options.height)
  2120. .attr(attrs)
  2121. .add(parent);
  2122. this.graphic.width = options.width;
  2123. this.graphic.height = options.height;
  2124. controllableMixin.render.call(this);
  2125. },
  2126. redraw: function (animation) {
  2127. var anchor = this.anchor(this.points[0]),
  2128. position = ControllableLabel.prototype.position.call(
  2129. this,
  2130. anchor
  2131. );
  2132. if (position) {
  2133. this.graphic[animation ? 'animate' : 'attr']({
  2134. x: position.x,
  2135. y: position.y
  2136. });
  2137. } else {
  2138. this.graphic.attr({
  2139. x: 0,
  2140. y: -9e9
  2141. });
  2142. }
  2143. this.graphic.placed = Boolean(position);
  2144. controllableMixin.redraw.call(this, animation);
  2145. }
  2146. }
  2147. );
  2148. return ControllableImage;
  2149. });
  2150. _registerModule(_modules, 'annotations/annotations.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['annotations/controllable/controllableMixin.js'], _modules['annotations/controllable/ControllableRect.js'], _modules['annotations/controllable/ControllableCircle.js'], _modules['annotations/controllable/ControllablePath.js'], _modules['annotations/controllable/ControllableImage.js'], _modules['annotations/controllable/ControllableLabel.js'], _modules['annotations/eventEmitterMixin.js'], _modules['annotations/MockPoint.js'], _modules['annotations/ControlPoint.js']], function (H, U, controllableMixin, ControllableRect, ControllableCircle, ControllablePath, ControllableImage, ControllableLabel, eventEmitterMixin, MockPoint, ControlPoint) {
  2151. /* *
  2152. *
  2153. * (c) 2009-2017 Highsoft, Black Label
  2154. *
  2155. * License: www.highcharts.com/license
  2156. *
  2157. * */
  2158. var defined = U.defined,
  2159. destroyObjectProperties = U.destroyObjectProperties,
  2160. erase = U.erase,
  2161. extend = U.extend,
  2162. pick = U.pick,
  2163. splat = U.splat,
  2164. wrap = U.wrap;
  2165. var merge = H.merge,
  2166. addEvent = H.addEvent,
  2167. fireEvent = H.fireEvent,
  2168. find = H.find,
  2169. reduce = H.reduce,
  2170. chartProto = H.Chart.prototype;
  2171. /* *********************************************************************
  2172. *
  2173. * ANNOTATION
  2174. *
  2175. ******************************************************************** */
  2176. /**
  2177. * @typedef {
  2178. * Annotation.ControllableCircle|
  2179. * Annotation.ControllableImage|
  2180. * Annotation.ControllablePath|
  2181. * Annotation.ControllableRect
  2182. * }
  2183. * Annotation.Shape
  2184. */
  2185. /**
  2186. * @typedef {Annotation.ControllableLabel} Annotation.Label
  2187. */
  2188. /**
  2189. * An annotation class which serves as a container for items like labels or
  2190. * shapes. Created items are positioned on the chart either by linking them to
  2191. * existing points or created mock points
  2192. *
  2193. * @class
  2194. * @name Highcharts.Annotation
  2195. *
  2196. * @param {Highcharts.Chart} chart a chart instance
  2197. * @param {Highcharts.AnnotationsOptions} userOptions the options object
  2198. */
  2199. var Annotation = H.Annotation = function (chart, userOptions) {
  2200. var labelsAndShapes;
  2201. /**
  2202. * The chart that the annotation belongs to.
  2203. *
  2204. * @type {Highcharts.Chart}
  2205. */
  2206. this.chart = chart;
  2207. /**
  2208. * The array of points which defines the annotation.
  2209. *
  2210. * @type {Array<Highcharts.Point>}
  2211. */
  2212. this.points = [];
  2213. /**
  2214. * The array of control points.
  2215. *
  2216. * @type {Array<Annotation.ControlPoint>}
  2217. */
  2218. this.controlPoints = [];
  2219. this.coll = 'annotations';
  2220. /**
  2221. * The array of labels which belong to the annotation.
  2222. *
  2223. * @type {Array<Annotation.Label>}
  2224. */
  2225. this.labels = [];
  2226. /**
  2227. * The array of shapes which belong to the annotation.
  2228. *
  2229. * @type {Array<Annotation.Shape>}
  2230. */
  2231. this.shapes = [];
  2232. /**
  2233. * The options for the annotations.
  2234. *
  2235. * @type {Highcharts.AnnotationsOptions}
  2236. */
  2237. this.options = merge(this.defaultOptions, userOptions);
  2238. /**
  2239. * The user options for the annotations.
  2240. *
  2241. * @type {Highcharts.AnnotationsOptions}
  2242. */
  2243. this.userOptions = userOptions;
  2244. // Handle labels and shapes - those are arrays
  2245. // Merging does not work with arrays (stores reference)
  2246. labelsAndShapes = this.getLabelsAndShapesOptions(
  2247. this.options,
  2248. userOptions
  2249. );
  2250. this.options.labels = labelsAndShapes.labels;
  2251. this.options.shapes = labelsAndShapes.shapes;
  2252. /**
  2253. * The callback that reports to the overlapping-labels module which
  2254. * labels it should account for.
  2255. *
  2256. * @name labelCollector
  2257. * @memberOf Annotation#
  2258. * @type {Function}
  2259. */
  2260. /**
  2261. * The group svg element.
  2262. *
  2263. * @name group
  2264. * @memberOf Annotation#
  2265. * @type {Highcharts.SVGElement}
  2266. */
  2267. /**
  2268. * The group svg element of the annotation's shapes.
  2269. *
  2270. * @name shapesGroup
  2271. * @memberOf Annotation#
  2272. * @type {Highcharts.SVGElement}
  2273. */
  2274. /**
  2275. * The group svg element of the annotation's labels.
  2276. *
  2277. * @name labelsGroup
  2278. * @memberOf Annotation#
  2279. * @type {Highcharts.SVGElement}
  2280. */
  2281. this.init(chart, this.options);
  2282. };
  2283. merge(
  2284. true,
  2285. Annotation.prototype,
  2286. controllableMixin,
  2287. eventEmitterMixin,
  2288. /** @lends Annotation# */
  2289. {
  2290. /**
  2291. * List of events for `annotation.options.events` that should not be
  2292. * added to `annotation.graphic` but to the `annotation`.
  2293. *
  2294. * @type {Array<string>}
  2295. */
  2296. nonDOMEvents: ['add', 'afterUpdate', 'drag', 'remove'],
  2297. /**
  2298. * A basic type of an annotation. It allows to add custom labels
  2299. * or shapes. The items can be tied to points, axis coordinates
  2300. * or chart pixel coordinates.
  2301. *
  2302. * @sample highcharts/annotations/basic/
  2303. * Basic annotations
  2304. * @sample highcharts/demo/annotations/
  2305. * Advanced annotations
  2306. * @sample highcharts/css/annotations
  2307. * Styled mode
  2308. * @sample highcharts/annotations-advanced/controllable
  2309. * Controllable items
  2310. * @sample {highstock} stock/annotations/fibonacci-retracements
  2311. * Custom annotation, Fibonacci retracement
  2312. *
  2313. * @type {Array<*>}
  2314. * @since 6.0.0
  2315. * @requires modules/annotations
  2316. * @optionparent annotations
  2317. */
  2318. defaultOptions: {
  2319. /**
  2320. * Sets an ID for an annotation. Can be user later when removing an
  2321. * annotation in [Chart#removeAnnotation(id)](
  2322. * /class-reference/Highcharts.Chart#removeAnnotation) method.
  2323. *
  2324. * @type {string|number}
  2325. * @apioption annotations.id
  2326. */
  2327. /**
  2328. * Whether the annotation is visible.
  2329. *
  2330. * @sample highcharts/annotations/visible/
  2331. * Set annotation visibility
  2332. */
  2333. visible: true,
  2334. /**
  2335. * Allow an annotation to be draggable by a user. Possible
  2336. * values are `"x"`, `"xy"`, `"y"` and `""` (disabled).
  2337. *
  2338. * @sample highcharts/annotations/draggable/
  2339. * Annotations draggable: 'xy'
  2340. *
  2341. * @type {string}
  2342. * @validvalue ["x", "xy", "y", ""]
  2343. */
  2344. draggable: 'xy',
  2345. /**
  2346. * Options for annotation's labels. Each label inherits options
  2347. * from the labelOptions object. An option from the labelOptions
  2348. * can be overwritten by config for a specific label.
  2349. *
  2350. * @requires modules/annotations
  2351. */
  2352. labelOptions: {
  2353. /**
  2354. * The alignment of the annotation's label. If right,
  2355. * the right side of the label should be touching the point.
  2356. *
  2357. * @sample highcharts/annotations/label-position/
  2358. * Set labels position
  2359. *
  2360. * @type {Highcharts.AlignValue}
  2361. */
  2362. align: 'center',
  2363. /**
  2364. * Whether to allow the annotation's labels to overlap.
  2365. * To make the labels less sensitive for overlapping,
  2366. * the can be set to 0.
  2367. *
  2368. * @sample highcharts/annotations/tooltip-like/
  2369. * Hide overlapping labels
  2370. */
  2371. allowOverlap: false,
  2372. /**
  2373. * The background color or gradient for the annotation's label.
  2374. *
  2375. * @sample highcharts/annotations/label-presentation/
  2376. * Set labels graphic options
  2377. *
  2378. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  2379. */
  2380. backgroundColor: 'rgba(0, 0, 0, 0.75)',
  2381. /**
  2382. * The border color for the annotation's label.
  2383. *
  2384. * @sample highcharts/annotations/label-presentation/
  2385. * Set labels graphic options
  2386. *
  2387. * @type {Highcharts.ColorString}
  2388. */
  2389. borderColor: 'black',
  2390. /**
  2391. * The border radius in pixels for the annotaiton's label.
  2392. *
  2393. * @sample highcharts/annotations/label-presentation/
  2394. * Set labels graphic options
  2395. */
  2396. borderRadius: 3,
  2397. /**
  2398. * The border width in pixels for the annotation's label
  2399. *
  2400. * @sample highcharts/annotations/label-presentation/
  2401. * Set labels graphic options
  2402. */
  2403. borderWidth: 1,
  2404. /**
  2405. * A class name for styling by CSS.
  2406. *
  2407. * @sample highcharts/css/annotations
  2408. * Styled mode annotations
  2409. *
  2410. * @since 6.0.5
  2411. */
  2412. className: '',
  2413. /**
  2414. * Whether to hide the annotation's label
  2415. * that is outside the plot area.
  2416. *
  2417. * @sample highcharts/annotations/label-crop-overflow/
  2418. * Crop or justify labels
  2419. */
  2420. crop: false,
  2421. /**
  2422. * The label's pixel distance from the point.
  2423. *
  2424. * @sample highcharts/annotations/label-position/
  2425. * Set labels position
  2426. *
  2427. * @type {number}
  2428. * @apioption annotations.labelOptions.distance
  2429. */
  2430. /**
  2431. * A
  2432. * [format](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
  2433. * string for the data label.
  2434. *
  2435. * @see [plotOptions.series.dataLabels.format](plotOptions.series.dataLabels.format.html)
  2436. *
  2437. * @sample highcharts/annotations/label-text/
  2438. * Set labels text
  2439. *
  2440. * @type {string}
  2441. * @apioption annotations.labelOptions.format
  2442. */
  2443. /**
  2444. * Alias for the format option.
  2445. *
  2446. * @see [format](annotations.labelOptions.format.html)
  2447. *
  2448. * @sample highcharts/annotations/label-text/
  2449. * Set labels text
  2450. *
  2451. * @type {string}
  2452. * @apioption annotations.labelOptions.text
  2453. */
  2454. /**
  2455. * Callback JavaScript function to format the annotation's
  2456. * label. Note that if a `format` or `text` are defined, the
  2457. * format or text take precedence and the formatter is ignored.
  2458. * `This` refers to a point object.
  2459. *
  2460. * @sample highcharts/annotations/label-text/
  2461. * Set labels text
  2462. *
  2463. * @type {Highcharts.FormatterCallbackFunction<Highcharts.Point>}
  2464. * @default function () { return defined(this.y) ? this.y : 'Annotation label'; }
  2465. */
  2466. formatter: function () {
  2467. return defined(this.y) ? this.y : 'Annotation label';
  2468. },
  2469. /**
  2470. * How to handle the annotation's label that flow outside the
  2471. * plot area. The justify option aligns the label inside the
  2472. * plot area.
  2473. *
  2474. * @sample highcharts/annotations/label-crop-overflow/
  2475. * Crop or justify labels
  2476. *
  2477. * @validvalue ["allow", "justify"]
  2478. */
  2479. overflow: 'justify',
  2480. /**
  2481. * When either the borderWidth or the backgroundColor is set,
  2482. * this is the padding within the box.
  2483. *
  2484. * @sample highcharts/annotations/label-presentation/
  2485. * Set labels graphic options
  2486. */
  2487. padding: 5,
  2488. /**
  2489. * The shadow of the box. The shadow can be an object
  2490. * configuration containing `color`, `offsetX`, `offsetY`,
  2491. * `opacity` and `width`.
  2492. *
  2493. * @sample highcharts/annotations/label-presentation/
  2494. * Set labels graphic options
  2495. *
  2496. * @type {boolean|Highcharts.ShadowOptionsObject}
  2497. */
  2498. shadow: false,
  2499. /**
  2500. * The name of a symbol to use for the border around the label.
  2501. * Symbols are predefined functions on the Renderer object.
  2502. *
  2503. * @sample highcharts/annotations/shapes/
  2504. * Available shapes for labels
  2505. */
  2506. shape: 'callout',
  2507. /**
  2508. * Styles for the annotation's label.
  2509. *
  2510. * @see [plotOptions.series.dataLabels.style](plotOptions.series.dataLabels.style.html)
  2511. *
  2512. * @sample highcharts/annotations/label-presentation/
  2513. * Set labels graphic options
  2514. *
  2515. * @type {Highcharts.CSSObject}
  2516. */
  2517. style: {
  2518. /** @ignore */
  2519. fontSize: '11px',
  2520. /** @ignore */
  2521. fontWeight: 'normal',
  2522. /** @ignore */
  2523. color: 'contrast'
  2524. },
  2525. /**
  2526. * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
  2527. * to render the annotation's label.
  2528. */
  2529. useHTML: false,
  2530. /**
  2531. * The vertical alignment of the annotation's label.
  2532. *
  2533. * @sample highcharts/annotations/label-position/
  2534. * Set labels position
  2535. *
  2536. * @type {Highcharts.VerticalAlignValue}
  2537. */
  2538. verticalAlign: 'bottom',
  2539. /**
  2540. * The x position offset of the label relative to the point.
  2541. * Note that if a `distance` is defined, the distance takes
  2542. * precedence over `x` and `y` options.
  2543. *
  2544. * @sample highcharts/annotations/label-position/
  2545. * Set labels position
  2546. */
  2547. x: 0,
  2548. /**
  2549. * The y position offset of the label relative to the point.
  2550. * Note that if a `distance` is defined, the distance takes
  2551. * precedence over `x` and `y` options.
  2552. *
  2553. * @sample highcharts/annotations/label-position/
  2554. * Set labels position
  2555. */
  2556. y: -16
  2557. },
  2558. /**
  2559. * An array of labels for the annotation. For options that apply to
  2560. * multiple labels, they can be added to the
  2561. * [labelOptions](annotations.labelOptions.html).
  2562. *
  2563. * @type {Array<*>}
  2564. * @extends annotations.labelOptions
  2565. * @apioption annotations.labels
  2566. */
  2567. /**
  2568. * This option defines the point to which the label will be
  2569. * connected. It can be either the point which exists in the
  2570. * series - it is referenced by the point's id - or a new point with
  2571. * defined x, y properties and optionally axes.
  2572. *
  2573. * @sample highcharts/annotations/mock-point/
  2574. * Attach annotation to a mock point
  2575. *
  2576. * @type {string|Highcharts.MockPointOptionsObject}
  2577. * @requires modules/annotations
  2578. * @apioption annotations.labels.point
  2579. */
  2580. /**
  2581. * The x position of the point. Units can be either in axis
  2582. * or chart pixel coordinates.
  2583. *
  2584. * @type {number}
  2585. * @apioption annotations.labels.point.x
  2586. */
  2587. /**
  2588. * The y position of the point. Units can be either in axis
  2589. * or chart pixel coordinates.
  2590. *
  2591. * @type {number}
  2592. * @apioption annotations.labels.point.y
  2593. */
  2594. /**
  2595. * This number defines which xAxis the point is connected to. It
  2596. * refers to either the axis id or the index of the axis in the
  2597. * xAxis array. If the option is not configured or the axis is not
  2598. * found the point's x coordinate refers to the chart pixels.
  2599. *
  2600. * @type {number|string}
  2601. * @apioption annotations.labels.point.xAxis
  2602. */
  2603. /**
  2604. * This number defines which yAxis the point is connected to. It
  2605. * refers to either the axis id or the index of the axis in the
  2606. * yAxis array. If the option is not configured or the axis is not
  2607. * found the point's y coordinate refers to the chart pixels.
  2608. *
  2609. * @type {number|string}
  2610. * @apioption annotations.labels.point.yAxis
  2611. */
  2612. /**
  2613. * An array of shapes for the annotation. For options that apply to
  2614. * multiple shapes, then can be added to the
  2615. * [shapeOptions](annotations.shapeOptions.html).
  2616. *
  2617. * @type {Array<*>}
  2618. * @extends annotations.shapeOptions
  2619. * @apioption annotations.shapes
  2620. */
  2621. /**
  2622. * This option defines the point to which the shape will be
  2623. * connected. It can be either the point which exists in the
  2624. * series - it is referenced by the point's id - or a new point with
  2625. * defined x, y properties and optionally axes.
  2626. *
  2627. * @type {string|Highcharts.MockPointOptionsObject}
  2628. * @extends annotations.labels.point
  2629. * @apioption annotations.shapes.point
  2630. */
  2631. /**
  2632. * An array of points for the shape. This option is available for
  2633. * shapes which can use multiple points such as path. A point can be
  2634. * either a point object or a point's id.
  2635. *
  2636. * @see [annotations.shapes.point](annotations.shapes.point.html)
  2637. *
  2638. * @type {Array<string|Highcharts.MockPointOptionsObject>}
  2639. * @extends annotations.labels.point
  2640. * @apioption annotations.shapes.points
  2641. */
  2642. /**
  2643. * Id of the marker which will be drawn at the final vertex of the
  2644. * path. Custom markers can be defined in defs property.
  2645. *
  2646. * @see [defs.markers](defs.markers.html)
  2647. *
  2648. * @sample highcharts/annotations/custom-markers/
  2649. * Define a custom marker for annotations
  2650. *
  2651. * @type {string}
  2652. * @apioption annotations.shapes.markerEnd
  2653. */
  2654. /**
  2655. * Id of the marker which will be drawn at the first vertex of the
  2656. * path. Custom markers can be defined in defs property.
  2657. *
  2658. * @see [defs.markers](defs.markers.html)
  2659. *
  2660. * @sample {highcharts} highcharts/annotations/custom-markers/
  2661. * Define a custom marker for annotations
  2662. *
  2663. * @type {string}
  2664. * @apioption annotations.shapes.markerStart
  2665. */
  2666. /**
  2667. * Options for annotation's shapes. Each shape inherits options from
  2668. * the shapeOptions object. An option from the shapeOptions can be
  2669. * overwritten by config for a specific shape.
  2670. *
  2671. * @requires modules/annotations
  2672. */
  2673. shapeOptions: {
  2674. /**
  2675. * The width of the shape.
  2676. *
  2677. * @sample highcharts/annotations/shape/
  2678. * Basic shape annotation
  2679. *
  2680. * @type {number}
  2681. * @apioption annotations.shapeOptions.width
  2682. **/
  2683. /**
  2684. * The height of the shape.
  2685. *
  2686. * @sample highcharts/annotations/shape/
  2687. * Basic shape annotation
  2688. *
  2689. * @type {number}
  2690. * @apioption annotations.shapeOptions.height
  2691. */
  2692. /**
  2693. * The type of the shape, e.g. circle or rectangle.
  2694. *
  2695. * @sample highcharts/annotations/shape/
  2696. * Basic shape annotation
  2697. *
  2698. * @type {string}
  2699. * @default 'rect'
  2700. * @apioption annotations.shapeOptions.type
  2701. */
  2702. /**
  2703. * The color of the shape's stroke.
  2704. *
  2705. * @sample highcharts/annotations/shape/
  2706. * Basic shape annotation
  2707. *
  2708. * @type {Highcharts.ColorString}
  2709. */
  2710. stroke: 'rgba(0, 0, 0, 0.75)',
  2711. /**
  2712. * The pixel stroke width of the shape.
  2713. *
  2714. * @sample highcharts/annotations/shape/
  2715. * Basic shape annotation
  2716. */
  2717. strokeWidth: 1,
  2718. /**
  2719. * The color of the shape's fill.
  2720. *
  2721. * @sample highcharts/annotations/shape/
  2722. * Basic shape annotation
  2723. *
  2724. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  2725. */
  2726. fill: 'rgba(0, 0, 0, 0.75)',
  2727. /**
  2728. * The radius of the shape.
  2729. *
  2730. * @sample highcharts/annotations/shape/
  2731. * Basic shape annotation
  2732. */
  2733. r: 0,
  2734. /**
  2735. * Defines additional snapping area around an annotation
  2736. * making this annotation to focus. Defined in pixels.
  2737. */
  2738. snap: 2
  2739. },
  2740. /**
  2741. * Options for annotation's control points. Each control point
  2742. * inherits options from controlPointOptions object.
  2743. * Options from the controlPointOptions can be overwritten
  2744. * by options in a specific control point.
  2745. *
  2746. * @type {Annotation.ControlPoint.Options}
  2747. * @requires modules/annotations
  2748. * @apioption annotations.controlPointOptions
  2749. */
  2750. controlPointOptions: {
  2751. /**
  2752. * @function {Annotation.ControlPoint.Positioner}
  2753. * @apioption annotations.controlPointOptions.positioner
  2754. */
  2755. symbol: 'circle',
  2756. width: 10,
  2757. height: 10,
  2758. style: {
  2759. stroke: 'black',
  2760. 'stroke-width': 2,
  2761. fill: 'white'
  2762. },
  2763. visible: false,
  2764. events: {}
  2765. },
  2766. /**
  2767. * Event callback when annotation is added to the chart.
  2768. *
  2769. * @type {Highcharts.EventCallbackFunction<Highcharts.Annotation>}
  2770. * @since 7.1.0
  2771. * @apioption annotations.events.add
  2772. */
  2773. /**
  2774. * Event callback when annotation is updated (e.g. drag and
  2775. * droppped or resized by control points).
  2776. *
  2777. * @type {Highcharts.EventCallbackFunction<Highcharts.Annotation>}
  2778. * @since 7.1.0
  2779. * @apioption annotations.events.afterUpdate
  2780. */
  2781. /**
  2782. * Event callback when annotation is removed from the chart.
  2783. *
  2784. * @type {Highcharts.EventCallbackFunction<Highcharts.Annotation>}
  2785. * @since 7.1.0
  2786. * @apioption annotations.events.remove
  2787. */
  2788. /**
  2789. * Events available in annotations.
  2790. *
  2791. * @requires modules/annotations
  2792. */
  2793. events: {},
  2794. /**
  2795. * The Z index of the annotation.
  2796. */
  2797. zIndex: 6
  2798. },
  2799. /**
  2800. * Initialize the annotation.
  2801. *
  2802. * @param {Highcharts.Chart}
  2803. * The chart
  2804. * @param {Highcharts.AnnotationsOptions}
  2805. * The user options for the annotation
  2806. */
  2807. init: function () {
  2808. this.linkPoints();
  2809. this.addControlPoints();
  2810. this.addShapes();
  2811. this.addLabels();
  2812. this.addClipPaths();
  2813. this.setLabelCollector();
  2814. },
  2815. getLabelsAndShapesOptions: function (baseOptions, newOptions) {
  2816. var mergedOptions = {};
  2817. ['labels', 'shapes'].forEach(function (name) {
  2818. if (baseOptions[name]) {
  2819. mergedOptions[name] = splat(newOptions[name]).map(
  2820. function (basicOptions, i) {
  2821. return merge(baseOptions[name][i], basicOptions);
  2822. }
  2823. );
  2824. }
  2825. });
  2826. return mergedOptions;
  2827. },
  2828. addShapes: function () {
  2829. (this.options.shapes || []).forEach(function (shapeOptions, i) {
  2830. var shape = this.initShape(shapeOptions, i);
  2831. merge(true, this.options.shapes[i], shape.options);
  2832. }, this);
  2833. },
  2834. addLabels: function () {
  2835. (this.options.labels || []).forEach(function (labelsOptions, i) {
  2836. var labels = this.initLabel(labelsOptions, i);
  2837. merge(true, this.options.labels[i], labels.options);
  2838. }, this);
  2839. },
  2840. addClipPaths: function () {
  2841. this.setClipAxes();
  2842. if (this.clipXAxis && this.clipYAxis) {
  2843. this.clipRect = this.chart.renderer.clipRect(
  2844. this.getClipBox()
  2845. );
  2846. }
  2847. },
  2848. setClipAxes: function () {
  2849. var xAxes = this.chart.xAxis,
  2850. yAxes = this.chart.yAxis,
  2851. linkedAxes = reduce(
  2852. (this.options.labels || [])
  2853. .concat(this.options.shapes || []),
  2854. function (axes, labelOrShape) {
  2855. return [
  2856. xAxes[
  2857. labelOrShape &&
  2858. labelOrShape.point &&
  2859. labelOrShape.point.xAxis
  2860. ] || axes[0],
  2861. yAxes[
  2862. labelOrShape &&
  2863. labelOrShape.point &&
  2864. labelOrShape.point.yAxis
  2865. ] || axes[1]
  2866. ];
  2867. },
  2868. []
  2869. );
  2870. this.clipXAxis = linkedAxes[0];
  2871. this.clipYAxis = linkedAxes[1];
  2872. },
  2873. getClipBox: function () {
  2874. return {
  2875. x: this.clipXAxis.left,
  2876. y: this.clipYAxis.top,
  2877. width: this.clipXAxis.width,
  2878. height: this.clipYAxis.height
  2879. };
  2880. },
  2881. setLabelCollector: function () {
  2882. var annotation = this;
  2883. annotation.labelCollector = function () {
  2884. return annotation.labels.reduce(
  2885. function (labels, label) {
  2886. if (!label.options.allowOverlap) {
  2887. labels.push(label.graphic);
  2888. }
  2889. return labels;
  2890. },
  2891. []
  2892. );
  2893. };
  2894. annotation.chart.labelCollectors.push(
  2895. annotation.labelCollector
  2896. );
  2897. },
  2898. /**
  2899. * Set an annotation options.
  2900. *
  2901. * @param {Highcharts.AnnotationsOptions} - user options for an annotation
  2902. */
  2903. setOptions: function (userOptions) {
  2904. this.options = merge(this.defaultOptions, userOptions);
  2905. },
  2906. redraw: function (animation) {
  2907. this.linkPoints();
  2908. if (!this.graphic) {
  2909. this.render();
  2910. }
  2911. if (this.clipRect) {
  2912. this.clipRect.animate(this.getClipBox());
  2913. }
  2914. this.redrawItems(this.shapes, animation);
  2915. this.redrawItems(this.labels, animation);
  2916. controllableMixin.redraw.call(this, animation);
  2917. },
  2918. /**
  2919. * @param {Array<(Annotation.Label|Annotation.Shape)>} items
  2920. * @param {boolean} [animation]
  2921. */
  2922. redrawItems: function (items, animation) {
  2923. var i = items.length;
  2924. // needs a backward loop
  2925. // labels/shapes array might be modified
  2926. // due to destruction of the item
  2927. while (i--) {
  2928. this.redrawItem(items[i], animation);
  2929. }
  2930. },
  2931. render: function () {
  2932. var renderer = this.chart.renderer;
  2933. this.graphic = renderer
  2934. .g('annotation')
  2935. .attr({
  2936. zIndex: this.options.zIndex,
  2937. visibility: this.options.visible ?
  2938. 'visible' :
  2939. 'hidden'
  2940. })
  2941. .add();
  2942. this.shapesGroup = renderer
  2943. .g('annotation-shapes')
  2944. .add(this.graphic)
  2945. .clip(this.chart.plotBoxClip);
  2946. this.labelsGroup = renderer
  2947. .g('annotation-labels')
  2948. .attr({
  2949. // hideOverlappingLabels requires translation
  2950. translateX: 0,
  2951. translateY: 0
  2952. })
  2953. .add(this.graphic);
  2954. if (this.clipRect) {
  2955. this.graphic.clip(this.clipRect);
  2956. }
  2957. this.addEvents();
  2958. controllableMixin.render.call(this);
  2959. },
  2960. /**
  2961. * Set the annotation's visibility.
  2962. *
  2963. * @param {Boolean} [visible] - Whether to show or hide an annotation.
  2964. * If the param is omitted, the annotation's visibility is toggled.
  2965. */
  2966. setVisibility: function (visibility) {
  2967. var options = this.options,
  2968. visible = pick(visibility, !options.visible);
  2969. this.graphic.attr(
  2970. 'visibility',
  2971. visible ? 'visible' : 'hidden'
  2972. );
  2973. if (!visible) {
  2974. this.setControlPointsVisibility(false);
  2975. }
  2976. options.visible = visible;
  2977. },
  2978. setControlPointsVisibility: function (visible) {
  2979. var setItemControlPointsVisibility = function (item) {
  2980. item.setControlPointsVisibility(visible);
  2981. };
  2982. controllableMixin.setControlPointsVisibility.call(
  2983. this,
  2984. visible
  2985. );
  2986. this.shapes.forEach(setItemControlPointsVisibility);
  2987. this.labels.forEach(setItemControlPointsVisibility);
  2988. },
  2989. /**
  2990. * Destroy the annotation. This function does not touch the chart
  2991. * that the annotation belongs to (all annotations are kept in
  2992. * the chart.annotations array) - it is recommended to use
  2993. * {@link Highcharts.Chart#removeAnnotation} instead.
  2994. */
  2995. destroy: function () {
  2996. var chart = this.chart,
  2997. destroyItem = function (item) {
  2998. item.destroy();
  2999. };
  3000. this.labels.forEach(destroyItem);
  3001. this.shapes.forEach(destroyItem);
  3002. this.clipXAxis = null;
  3003. this.clipYAxis = null;
  3004. erase(chart.labelCollectors, this.labelCollector);
  3005. eventEmitterMixin.destroy.call(this);
  3006. controllableMixin.destroy.call(this);
  3007. destroyObjectProperties(this, chart);
  3008. },
  3009. /**
  3010. * See {@link Highcharts.Chart#removeAnnotation}.
  3011. */
  3012. remove: function () {
  3013. // Let chart.update() remove annoations on demand
  3014. return this.chart.removeAnnotation(this);
  3015. },
  3016. update: function (userOptions) {
  3017. var chart = this.chart,
  3018. labelsAndShapes = this.getLabelsAndShapesOptions(
  3019. this.userOptions,
  3020. userOptions
  3021. ),
  3022. userOptionsIndex = chart.annotations.indexOf(this),
  3023. options = H.merge(true, this.userOptions, userOptions);
  3024. options.labels = labelsAndShapes.labels;
  3025. options.shapes = labelsAndShapes.shapes;
  3026. this.destroy();
  3027. this.constructor(chart, options);
  3028. // Update options in chart options, used in exporting (#9767):
  3029. chart.options.annotations[userOptionsIndex] = options;
  3030. this.isUpdating = true;
  3031. this.redraw();
  3032. this.isUpdating = false;
  3033. fireEvent(this, 'afterUpdate');
  3034. },
  3035. /* *************************************************************
  3036. * ITEM SECTION
  3037. * Contains methods for handling a single item in an annotation
  3038. **************************************************************** */
  3039. /**
  3040. * Initialisation of a single shape
  3041. *
  3042. * @param {Object} shapeOptions - a confg object for a single shape
  3043. */
  3044. initShape: function (shapeOptions, index) {
  3045. var options = merge(
  3046. this.options.shapeOptions,
  3047. {
  3048. controlPointOptions: this.options.controlPointOptions
  3049. },
  3050. shapeOptions
  3051. ),
  3052. shape = new Annotation.shapesMap[options.type](
  3053. this,
  3054. options,
  3055. index
  3056. );
  3057. shape.itemType = 'shape';
  3058. this.shapes.push(shape);
  3059. return shape;
  3060. },
  3061. /**
  3062. * Initialisation of a single label
  3063. *
  3064. * @param {Object} labelOptions
  3065. **/
  3066. initLabel: function (labelOptions, index) {
  3067. var options = merge(
  3068. this.options.labelOptions,
  3069. {
  3070. controlPointOptions: this.options.controlPointOptions
  3071. },
  3072. labelOptions
  3073. ),
  3074. label = new ControllableLabel(
  3075. this,
  3076. options,
  3077. index
  3078. );
  3079. label.itemType = 'label';
  3080. this.labels.push(label);
  3081. return label;
  3082. },
  3083. /**
  3084. * Redraw a single item.
  3085. *
  3086. * @param {Annotation.Label|Annotation.Shape} item
  3087. * @param {boolean} [animation]
  3088. */
  3089. redrawItem: function (item, animation) {
  3090. item.linkPoints();
  3091. if (!item.shouldBeDrawn()) {
  3092. this.destroyItem(item);
  3093. } else {
  3094. if (!item.graphic) {
  3095. this.renderItem(item);
  3096. }
  3097. item.redraw(
  3098. pick(animation, true) && item.graphic.placed
  3099. );
  3100. if (item.points.length) {
  3101. this.adjustVisibility(item);
  3102. }
  3103. }
  3104. },
  3105. /**
  3106. * Hide or show annotaiton attached to points.
  3107. *
  3108. * @param {Annotation.Label|Annotation.Shape} item
  3109. */
  3110. adjustVisibility: function (item) { // #9481
  3111. var hasVisiblePoints = false,
  3112. label = item.graphic;
  3113. item.points.forEach(function (point) {
  3114. if (
  3115. point.series.visible !== false &&
  3116. point.visible !== false
  3117. ) {
  3118. hasVisiblePoints = true;
  3119. }
  3120. });
  3121. if (!hasVisiblePoints) {
  3122. label.hide();
  3123. } else if (label.visibility === 'hidden') {
  3124. label.show();
  3125. }
  3126. },
  3127. /**
  3128. * Destroy a single item.
  3129. *
  3130. * @param {Annotation.Label|Annotation.Shape} item
  3131. */
  3132. destroyItem: function (item) {
  3133. // erase from shapes or labels array
  3134. erase(this[item.itemType + 's'], item);
  3135. item.destroy();
  3136. },
  3137. /**
  3138. * @private
  3139. */
  3140. renderItem: function (item) {
  3141. item.render(
  3142. item.itemType === 'label' ?
  3143. this.labelsGroup :
  3144. this.shapesGroup
  3145. );
  3146. }
  3147. }
  3148. );
  3149. /**
  3150. * An object uses for mapping between a shape type and a constructor.
  3151. * To add a new shape type extend this object with type name as a key
  3152. * and a constructor as its value.
  3153. */
  3154. Annotation.shapesMap = {
  3155. 'rect': ControllableRect,
  3156. 'circle': ControllableCircle,
  3157. 'path': ControllablePath,
  3158. 'image': ControllableImage
  3159. };
  3160. Annotation.types = {};
  3161. Annotation.MockPoint = MockPoint;
  3162. Annotation.ControlPoint = ControlPoint;
  3163. H.extendAnnotation = function (
  3164. Constructor,
  3165. BaseConstructor,
  3166. prototype,
  3167. defaultOptions
  3168. ) {
  3169. BaseConstructor = BaseConstructor || Annotation;
  3170. merge(
  3171. true,
  3172. Constructor.prototype,
  3173. BaseConstructor.prototype,
  3174. prototype
  3175. );
  3176. Constructor.prototype.defaultOptions = merge(
  3177. Constructor.prototype.defaultOptions,
  3178. defaultOptions || {}
  3179. );
  3180. };
  3181. /* *********************************************************************
  3182. *
  3183. * EXTENDING CHART PROTOTYPE
  3184. *
  3185. ******************************************************************** */
  3186. extend(chartProto, /** @lends Highcharts.Chart# */ {
  3187. initAnnotation: function (userOptions) {
  3188. var Constructor =
  3189. Annotation.types[userOptions.type] || Annotation,
  3190. annotation = new Constructor(this, userOptions);
  3191. this.annotations.push(annotation);
  3192. return annotation;
  3193. },
  3194. /**
  3195. * Add an annotation to the chart after render time.
  3196. *
  3197. * @param {Highcharts.AnnotationsOptions} options
  3198. * The annotation options for the new, detailed annotation.
  3199. * @param {boolean} [redraw]
  3200. *
  3201. * @return {Highcharts.Annotation} - The newly generated annotation.
  3202. */
  3203. addAnnotation: function (userOptions, redraw) {
  3204. var annotation = this.initAnnotation(userOptions);
  3205. this.options.annotations.push(annotation.options);
  3206. if (pick(redraw, true)) {
  3207. annotation.redraw();
  3208. }
  3209. return annotation;
  3210. },
  3211. /**
  3212. * Remove an annotation from the chart.
  3213. *
  3214. * @param {String|Number|Annotation} idOrAnnotation - The annotation's id or
  3215. * direct annotation object.
  3216. */
  3217. removeAnnotation: function (idOrAnnotation) {
  3218. var annotations = this.annotations,
  3219. annotation = idOrAnnotation.coll === 'annotations' ?
  3220. idOrAnnotation :
  3221. find(
  3222. annotations,
  3223. function (annotation) {
  3224. return annotation.options.id === idOrAnnotation;
  3225. }
  3226. );
  3227. if (annotation) {
  3228. fireEvent(annotation, 'remove');
  3229. erase(this.options.annotations, annotation.options);
  3230. erase(annotations, annotation);
  3231. annotation.destroy();
  3232. }
  3233. },
  3234. drawAnnotations: function () {
  3235. this.plotBoxClip.attr(this.plotBox);
  3236. this.annotations.forEach(function (annotation) {
  3237. annotation.redraw();
  3238. });
  3239. }
  3240. });
  3241. // Let chart.update() update annotations
  3242. chartProto.collectionsWithUpdate.push('annotations');
  3243. // Let chart.update() create annoations on demand
  3244. chartProto.collectionsWithInit.annotations = [chartProto.addAnnotation];
  3245. chartProto.callbacks.push(function (chart) {
  3246. chart.annotations = [];
  3247. if (!chart.options.annotations) {
  3248. chart.options.annotations = [];
  3249. }
  3250. chart.plotBoxClip = this.renderer.clipRect(this.plotBox);
  3251. chart.controlPointsGroup = chart.renderer
  3252. .g('control-points')
  3253. .attr({ zIndex: 99 })
  3254. .clip(chart.plotBoxClip)
  3255. .add();
  3256. chart.options.annotations.forEach(function (annotationOptions, i) {
  3257. var annotation = chart.initAnnotation(annotationOptions);
  3258. chart.options.annotations[i] = annotation.options;
  3259. });
  3260. chart.drawAnnotations();
  3261. addEvent(chart, 'redraw', chart.drawAnnotations);
  3262. addEvent(chart, 'destroy', function () {
  3263. chart.plotBoxClip.destroy();
  3264. chart.controlPointsGroup.destroy();
  3265. });
  3266. });
  3267. wrap(
  3268. H.Pointer.prototype,
  3269. 'onContainerMouseDown',
  3270. function (proceed) {
  3271. if (!this.chart.hasDraggedAnnotation) {
  3272. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  3273. }
  3274. }
  3275. );
  3276. });
  3277. _registerModule(_modules, 'mixins/navigation.js', [], function () {
  3278. /**
  3279. *
  3280. * (c) 2010-2018 Paweł Fus
  3281. *
  3282. * License: www.highcharts.com/license
  3283. *
  3284. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  3285. *
  3286. * */
  3287. var chartNavigation = {
  3288. /**
  3289. * Initializes `chart.navigation` object which delegates `update()` methods
  3290. * to all other common classes (used in exporting and navigationBindings).
  3291. *
  3292. * @private
  3293. * @param {Highcharts.Chart} chart
  3294. * The chart instance.
  3295. * @return {void}
  3296. */
  3297. initUpdate: function (chart) {
  3298. if (!chart.navigation) {
  3299. chart.navigation = {
  3300. updates: [],
  3301. update: function (options, redraw) {
  3302. this.updates.forEach(function (updateConfig) {
  3303. updateConfig.update.call(updateConfig.context, options, redraw);
  3304. });
  3305. }
  3306. };
  3307. }
  3308. },
  3309. /**
  3310. * Registers an `update()` method in the `chart.navigation` object.
  3311. *
  3312. * @private
  3313. * @param {Highcharts.ChartNavigationUpdateFunction} update
  3314. * The `update()` method that will be called in `chart.update()`.
  3315. * @param {Highcharts.Chart} chart
  3316. * The chart instance. `update()` will use that as a context
  3317. * (`this`).
  3318. * @return {void}
  3319. */
  3320. addUpdate: function (update, chart) {
  3321. if (!chart.navigation) {
  3322. this.initUpdate(chart);
  3323. }
  3324. chart.navigation.updates.push({
  3325. update: update,
  3326. context: chart
  3327. });
  3328. }
  3329. };
  3330. return chartNavigation;
  3331. });
  3332. _registerModule(_modules, 'annotations/navigationBindings.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['mixins/navigation.js']], function (H, U, chartNavigationMixin) {
  3333. /* *
  3334. *
  3335. * (c) 2009-2017 Highsoft, Black Label
  3336. *
  3337. * License: www.highcharts.com/license
  3338. *
  3339. * */
  3340. var attr = U.attr,
  3341. extend = U.extend,
  3342. isArray = U.isArray,
  3343. isNumber = U.isNumber,
  3344. isObject = U.isObject,
  3345. objectEach = U.objectEach,
  3346. pick = U.pick;
  3347. var doc = H.doc,
  3348. win = H.win,
  3349. addEvent = H.addEvent,
  3350. merge = H.merge,
  3351. fireEvent = H.fireEvent,
  3352. PREFIX = 'highcharts-';
  3353. // IE 9-11 polyfill for Element.closest():
  3354. function closestPolyfill(el, s) {
  3355. var ElementProto = win.Element.prototype,
  3356. elementMatches =
  3357. ElementProto.matches ||
  3358. ElementProto.msMatchesSelector ||
  3359. ElementProto.webkitMatchesSelector,
  3360. ret = null;
  3361. if (ElementProto.closest) {
  3362. ret = ElementProto.closest.call(el, s);
  3363. } else {
  3364. do {
  3365. if (elementMatches.call(el, s)) {
  3366. return el;
  3367. }
  3368. el = el.parentElement || el.parentNode;
  3369. } while (el !== null && el.nodeType === 1);
  3370. }
  3371. return ret;
  3372. }
  3373. /**
  3374. * @private
  3375. * @interface bindingsUtils
  3376. */
  3377. var bindingsUtils = {
  3378. /**
  3379. * Update size of background (rect) in some annotations: Measure, Simple
  3380. * Rect.
  3381. *
  3382. * @private
  3383. * @function bindingsUtils.updateRectSize
  3384. *
  3385. * @param {global.Event} event
  3386. * Normalized browser event
  3387. *
  3388. * @param {Highcharts.Annotation} annotation
  3389. * Annotation to be updated
  3390. */
  3391. updateRectSize: function (event, annotation) {
  3392. var chart = annotation.chart,
  3393. options = annotation.options.typeOptions,
  3394. coords = chart.pointer.getCoordinates(event),
  3395. width = coords.xAxis[0].value - options.point.x,
  3396. height = options.point.y - coords.yAxis[0].value;
  3397. annotation.update({
  3398. typeOptions: {
  3399. background: {
  3400. width: chart.inverted ? height : width,
  3401. height: chart.inverted ? width : height
  3402. }
  3403. }
  3404. });
  3405. },
  3406. /**
  3407. * Get field type according to value
  3408. *
  3409. * @private
  3410. * @function bindingsUtils.getFieldType
  3411. *
  3412. * @param {*} value
  3413. * Atomic type (one of: string, number, boolean)
  3414. *
  3415. * @return {string}
  3416. * Field type (one of: text, number, checkbox)
  3417. */
  3418. getFieldType: function (value) {
  3419. return {
  3420. 'string': 'text',
  3421. 'number': 'number',
  3422. 'boolean': 'checkbox'
  3423. }[typeof value];
  3424. }
  3425. };
  3426. H.NavigationBindings = function (chart, options) {
  3427. this.chart = chart;
  3428. this.options = options;
  3429. this.eventsToUnbind = [];
  3430. this.container = doc.getElementsByClassName(
  3431. this.options.bindingsClassName
  3432. );
  3433. };
  3434. // Define which options from annotations should show up in edit box:
  3435. H.NavigationBindings.annotationsEditable = {
  3436. // `typeOptions` are always available
  3437. // Nested and shared options:
  3438. nestedOptions: {
  3439. labelOptions: ['style', 'format', 'backgroundColor'],
  3440. labels: ['style'],
  3441. label: ['style'],
  3442. style: ['fontSize', 'color'],
  3443. background: ['fill', 'strokeWidth', 'stroke'],
  3444. innerBackground: ['fill', 'strokeWidth', 'stroke'],
  3445. outerBackground: ['fill', 'strokeWidth', 'stroke'],
  3446. shapeOptions: ['fill', 'strokeWidth', 'stroke'],
  3447. shapes: ['fill', 'strokeWidth', 'stroke'],
  3448. line: ['strokeWidth', 'stroke'],
  3449. backgroundColors: [true],
  3450. connector: ['fill', 'strokeWidth', 'stroke'],
  3451. crosshairX: ['strokeWidth', 'stroke'],
  3452. crosshairY: ['strokeWidth', 'stroke']
  3453. },
  3454. // Simple shapes:
  3455. circle: ['shapes'],
  3456. verticalLine: [],
  3457. label: ['labelOptions'],
  3458. // Measure
  3459. measure: ['background', 'crosshairY', 'crosshairX'],
  3460. // Others:
  3461. fibonacci: [],
  3462. tunnel: ['background', 'line', 'height'],
  3463. pitchfork: ['innerBackground', 'outerBackground'],
  3464. rect: ['shapes'],
  3465. // Crooked lines, elliots, arrows etc:
  3466. crookedLine: []
  3467. };
  3468. // Define non editable fields per annotation, for example Rectangle inherits
  3469. // options from Measure, but crosshairs are not available
  3470. H.NavigationBindings.annotationsNonEditable = {
  3471. rectangle: ['crosshairX', 'crosshairY', 'label']
  3472. };
  3473. extend(H.NavigationBindings.prototype, {
  3474. // Private properties added by bindings:
  3475. // Active (selected) annotation that is editted through popup/forms
  3476. // activeAnnotation: Annotation
  3477. // Holder for current step, used on mouse move to update bound object
  3478. // mouseMoveEvent: function () {}
  3479. // Next event in `step` array to be called on chart's click
  3480. // nextEvent: function () {}
  3481. // Index in the `step` array of the current event
  3482. // stepIndex: 0
  3483. // Flag to determine if current binding has steps
  3484. // steps: true|false
  3485. // Bindings holder for all events
  3486. // selectedButton: {}
  3487. // Holder for user options, returned from `start` event, and passed on to
  3488. // `step`'s' and `end`.
  3489. // currentUserDetails: {}
  3490. /**
  3491. * Initi all events conencted to NavigationBindings.
  3492. *
  3493. * @private
  3494. * @function Highcharts.NavigationBindings#initEvents
  3495. */
  3496. initEvents: function () {
  3497. var navigation = this,
  3498. chart = navigation.chart,
  3499. bindingsContainer = navigation.container,
  3500. options = navigation.options;
  3501. // Shorthand object for getting events for buttons:
  3502. navigation.boundClassNames = {};
  3503. objectEach(options.bindings, function (value) {
  3504. navigation.boundClassNames[value.className] = value;
  3505. });
  3506. // Handle multiple containers with the same class names:
  3507. [].forEach.call(bindingsContainer, function (subContainer) {
  3508. navigation.eventsToUnbind.push(
  3509. addEvent(
  3510. subContainer,
  3511. 'click',
  3512. function (event) {
  3513. var bindings = navigation.getButtonEvents(
  3514. bindingsContainer,
  3515. event
  3516. );
  3517. if (bindings) {
  3518. navigation.bindingsButtonClick(
  3519. bindings.button,
  3520. bindings.events,
  3521. event
  3522. );
  3523. }
  3524. }
  3525. )
  3526. );
  3527. });
  3528. objectEach(options.events || {}, function (callback, eventName) {
  3529. if (H.isFunction(callback)) {
  3530. navigation.eventsToUnbind.push(
  3531. addEvent(
  3532. navigation,
  3533. eventName,
  3534. callback
  3535. )
  3536. );
  3537. }
  3538. });
  3539. navigation.eventsToUnbind.push(
  3540. addEvent(chart.container, 'click', function (e) {
  3541. if (
  3542. !chart.cancelClick &&
  3543. chart.isInsidePlot(
  3544. e.chartX - chart.plotLeft,
  3545. e.chartY - chart.plotTop
  3546. )
  3547. ) {
  3548. navigation.bindingsChartClick(this, e);
  3549. }
  3550. })
  3551. );
  3552. navigation.eventsToUnbind.push(
  3553. addEvent(chart.container, 'mousemove', function (e) {
  3554. navigation.bindingsContainerMouseMove(this, e);
  3555. })
  3556. );
  3557. },
  3558. /**
  3559. * Common chart.update() delegation, shared between bindings and exporting.
  3560. *
  3561. * @private
  3562. * @function Highcharts.NavigationBindings#initUpdate
  3563. */
  3564. initUpdate: function () {
  3565. var navigation = this;
  3566. chartNavigationMixin.addUpdate(
  3567. function (options) {
  3568. navigation.update(options);
  3569. },
  3570. this.chart
  3571. );
  3572. },
  3573. /**
  3574. * Hook for click on a button, method selcts/unselects buttons,
  3575. * then calls `bindings.init` callback.
  3576. *
  3577. * @private
  3578. * @function Highcharts.NavigationBindings#bindingsButtonClick
  3579. *
  3580. * @param {Highcharts.HTMLDOMElement} [button]
  3581. * Clicked button
  3582. *
  3583. * @param {object} [events]
  3584. * Events passed down from bindings (`init`, `start`, `step`, `end`)
  3585. *
  3586. * @param {global.Event} [clickEvent]
  3587. * Browser's click event
  3588. */
  3589. bindingsButtonClick: function (button, events, clickEvent) {
  3590. var navigation = this,
  3591. chart = navigation.chart;
  3592. if (navigation.selectedButtonElement) {
  3593. fireEvent(
  3594. navigation,
  3595. 'deselectButton',
  3596. { button: navigation.selectedButtonElement }
  3597. );
  3598. if (navigation.nextEvent) {
  3599. // Remove in-progress annotations adders:
  3600. if (
  3601. navigation.currentUserDetails &&
  3602. navigation.currentUserDetails.coll === 'annotations'
  3603. ) {
  3604. chart.removeAnnotation(navigation.currentUserDetails);
  3605. }
  3606. navigation.mouseMoveEvent = navigation.nextEvent = false;
  3607. }
  3608. }
  3609. navigation.selectedButton = events;
  3610. navigation.selectedButtonElement = button;
  3611. fireEvent(navigation, 'selectButton', { button: button });
  3612. // Call "init" event, for example to open modal window
  3613. if (events.init) {
  3614. events.init.call(navigation, button, clickEvent);
  3615. }
  3616. if (events.start || events.steps) {
  3617. chart.renderer.boxWrapper.addClass(PREFIX + 'draw-mode');
  3618. }
  3619. },
  3620. /**
  3621. * Hook for click on a chart, first click on a chart calls `start` event,
  3622. * then on all subsequent clicks iterate over `steps` array.
  3623. * When finished, calls `end` event.
  3624. *
  3625. * @private
  3626. * @function Highcharts.NavigationBindings#bindingsChartClick
  3627. *
  3628. * @param {Highcharts.Chart} chart
  3629. * Chart that click was performed on.
  3630. *
  3631. * @param {global.Event} clickEvent
  3632. * Browser's click event.
  3633. */
  3634. bindingsChartClick: function (chartContainer, clickEvent) {
  3635. var navigation = this,
  3636. chart = navigation.chart,
  3637. selectedButton = navigation.selectedButton,
  3638. svgContainer = chart.renderer.boxWrapper;
  3639. // Click outside popups, should close them and deselect the annotation
  3640. if (
  3641. navigation.activeAnnotation &&
  3642. !clickEvent.activeAnnotation &&
  3643. // Element could be removed in the child action, e.g. button
  3644. clickEvent.target.parentNode &&
  3645. // TO DO: Polyfill for IE11?
  3646. !closestPolyfill(clickEvent.target, '.' + PREFIX + 'popup')
  3647. ) {
  3648. fireEvent(navigation, 'closePopup');
  3649. navigation.deselectAnnotation();
  3650. }
  3651. if (!selectedButton || !selectedButton.start) {
  3652. return;
  3653. }
  3654. if (!navigation.nextEvent) {
  3655. // Call init method:
  3656. navigation.currentUserDetails = selectedButton.start.call(
  3657. navigation,
  3658. clickEvent
  3659. );
  3660. // If steps exists (e.g. Annotations), bind them:
  3661. if (selectedButton.steps) {
  3662. navigation.stepIndex = 0;
  3663. navigation.steps = true;
  3664. navigation.mouseMoveEvent = navigation.nextEvent =
  3665. selectedButton.steps[navigation.stepIndex];
  3666. } else {
  3667. fireEvent(
  3668. navigation,
  3669. 'deselectButton',
  3670. { button: navigation.selectedButtonElement }
  3671. );
  3672. svgContainer.removeClass(PREFIX + 'draw-mode');
  3673. navigation.steps = false;
  3674. navigation.selectedButton = null;
  3675. // First click is also the last one:
  3676. if (selectedButton.end) {
  3677. selectedButton.end.call(
  3678. navigation,
  3679. clickEvent,
  3680. navigation.currentUserDetails
  3681. );
  3682. }
  3683. }
  3684. } else {
  3685. navigation.nextEvent(
  3686. clickEvent,
  3687. navigation.currentUserDetails
  3688. );
  3689. if (navigation.steps) {
  3690. navigation.stepIndex++;
  3691. if (selectedButton.steps[navigation.stepIndex]) {
  3692. // If we have more steps, bind them one by one:
  3693. navigation.mouseMoveEvent = navigation.nextEvent =
  3694. selectedButton.steps[navigation.stepIndex];
  3695. } else {
  3696. fireEvent(
  3697. navigation,
  3698. 'deselectButton',
  3699. { button: navigation.selectedButtonElement }
  3700. );
  3701. svgContainer.removeClass(PREFIX + 'draw-mode');
  3702. // That was the last step, call end():
  3703. if (selectedButton.end) {
  3704. selectedButton.end.call(
  3705. navigation,
  3706. clickEvent,
  3707. navigation.currentUserDetails
  3708. );
  3709. }
  3710. navigation.nextEvent = false;
  3711. navigation.mouseMoveEvent = false;
  3712. navigation.selectedButton = null;
  3713. }
  3714. }
  3715. }
  3716. },
  3717. /**
  3718. * Hook for mouse move on a chart's container. It calls current step.
  3719. *
  3720. * @private
  3721. * @function Highcharts.NavigationBindings#bindingsContainerMouseMove
  3722. *
  3723. * @param {Highcharts.HTMLDOMElement} container
  3724. * Chart's container.
  3725. *
  3726. * @param {global.Event} moveEvent
  3727. * Browser's move event.
  3728. */
  3729. bindingsContainerMouseMove: function (container, moveEvent) {
  3730. if (this.mouseMoveEvent) {
  3731. this.mouseMoveEvent(
  3732. moveEvent,
  3733. this.currentUserDetails
  3734. );
  3735. }
  3736. },
  3737. /**
  3738. * Translate fields (e.g. `params.period` or `marker.styles.color`) to
  3739. * Highcharts options object (e.g. `{ params: { period } }`).
  3740. *
  3741. * @private
  3742. * @function Highcharts.NavigationBindings#fieldsToOptions
  3743. *
  3744. * @param {object} fields
  3745. * Fields from popup form.
  3746. *
  3747. * @param {object} config
  3748. * Default config to be modified.
  3749. *
  3750. * @return {object}
  3751. * Modified config
  3752. */
  3753. fieldsToOptions: function (fields, config) {
  3754. objectEach(fields, function (value, field) {
  3755. var parsedValue = parseFloat(value),
  3756. path = field.split('.'),
  3757. parent = config,
  3758. pathLength = path.length - 1;
  3759. // If it's a number (not "forma" options), parse it:
  3760. if (
  3761. isNumber(parsedValue) &&
  3762. !value.match(/px/g) &&
  3763. !field.match(/format/g)
  3764. ) {
  3765. value = parsedValue;
  3766. }
  3767. // Remove empty strings or values like 0
  3768. if (value !== '' && value !== 'undefined') {
  3769. path.forEach(function (name, index) {
  3770. var nextName = pick(path[index + 1], '');
  3771. if (pathLength === index) {
  3772. // Last index, put value:
  3773. parent[name] = value;
  3774. } else if (!parent[name]) {
  3775. // Create middle property:
  3776. parent[name] = nextName.match(/\d/g) ? [] : {};
  3777. parent = parent[name];
  3778. } else {
  3779. // Jump into next property
  3780. parent = parent[name];
  3781. }
  3782. });
  3783. }
  3784. });
  3785. return config;
  3786. },
  3787. /**
  3788. * Shorthand method to deselect an annotation.
  3789. *
  3790. * @function Highcharts.NavigationBindings#deselectAnnotation
  3791. */
  3792. deselectAnnotation: function () {
  3793. if (this.activeAnnotation) {
  3794. this.activeAnnotation.setControlPointsVisibility(false);
  3795. this.activeAnnotation = false;
  3796. }
  3797. },
  3798. /**
  3799. * Generates API config for popup in the same format as options for
  3800. * Annotation object.
  3801. *
  3802. * @function Highcharts.NavigationBindings#annotationToFields
  3803. *
  3804. * @param {Highcharts.Annotation} annotation
  3805. * Annotations object
  3806. *
  3807. * @return {object}
  3808. * Annotation options to be displayed in popup box
  3809. */
  3810. annotationToFields: function (annotation) {
  3811. var options = annotation.options,
  3812. editables = H.NavigationBindings.annotationsEditable,
  3813. nestedEditables = editables.nestedOptions,
  3814. getFieldType = this.utils.getFieldType,
  3815. type = pick(
  3816. options.type,
  3817. options.shapes && options.shapes[0] &&
  3818. options.shapes[0].type,
  3819. options.labels && options.labels[0] &&
  3820. options.labels[0].itemType,
  3821. 'label'
  3822. ),
  3823. nonEditables = H.NavigationBindings
  3824. .annotationsNonEditable[options.langKey] || [],
  3825. visualOptions = {
  3826. langKey: options.langKey,
  3827. type: type
  3828. };
  3829. /**
  3830. * Nested options traversing. Method goes down to the options and copies
  3831. * allowed options (with values) to new object, which is last parameter:
  3832. * "parent".
  3833. *
  3834. * @private
  3835. * @function Highcharts.NavigationBindings#annotationToFields.traverse
  3836. *
  3837. * @param {*} option
  3838. * Atomic type or object/array
  3839. *
  3840. * @param {string} key
  3841. * Option name, for example "visible" or "x", "y"
  3842. *
  3843. * @param {object} allowed
  3844. * Editables from H.NavigationBindings.annotationsEditable
  3845. *
  3846. * @param {object} parent
  3847. * Where new options will be assigned
  3848. */
  3849. function traverse(option, key, parentEditables, parent) {
  3850. var nextParent;
  3851. if (
  3852. parentEditables &&
  3853. nonEditables.indexOf(key) === -1 &&
  3854. (
  3855. (
  3856. parentEditables.indexOf &&
  3857. parentEditables.indexOf(key)
  3858. ) >= 0 ||
  3859. parentEditables[key] || // nested array
  3860. parentEditables === true // simple array
  3861. )
  3862. ) {
  3863. // Roots:
  3864. if (isArray(option)) {
  3865. parent[key] = [];
  3866. option.forEach(function (arrayOption, i) {
  3867. if (!isObject(arrayOption)) {
  3868. // Simple arrays, e.g. [String, Number, Boolean]
  3869. traverse(
  3870. arrayOption,
  3871. 0,
  3872. nestedEditables[key],
  3873. parent[key]
  3874. );
  3875. } else {
  3876. // Advanced arrays, e.g. [Object, Object]
  3877. parent[key][i] = {};
  3878. objectEach(
  3879. arrayOption,
  3880. function (nestedOption, nestedKey) {
  3881. traverse(
  3882. nestedOption,
  3883. nestedKey,
  3884. nestedEditables[key],
  3885. parent[key][i]
  3886. );
  3887. }
  3888. );
  3889. }
  3890. });
  3891. } else if (isObject(option)) {
  3892. nextParent = {};
  3893. if (isArray(parent)) {
  3894. parent.push(nextParent);
  3895. nextParent[key] = {};
  3896. nextParent = nextParent[key];
  3897. } else {
  3898. parent[key] = nextParent;
  3899. }
  3900. objectEach(option, function (nestedOption, nestedKey) {
  3901. traverse(
  3902. nestedOption,
  3903. nestedKey,
  3904. key === 0 ? parentEditables : nestedEditables[key],
  3905. nextParent
  3906. );
  3907. });
  3908. } else {
  3909. // Leaf:
  3910. if (key === 'format') {
  3911. parent[key] = [
  3912. H.format(
  3913. option,
  3914. annotation.labels[0].points[0]
  3915. ).toString(),
  3916. 'text'
  3917. ];
  3918. } else if (isArray(parent)) {
  3919. parent.push([option, getFieldType(option)]);
  3920. } else {
  3921. parent[key] = [option, getFieldType(option)];
  3922. }
  3923. }
  3924. }
  3925. }
  3926. objectEach(options, function (option, key) {
  3927. if (key === 'typeOptions') {
  3928. visualOptions[key] = {};
  3929. objectEach(options[key], function (typeOption, typeKey) {
  3930. traverse(
  3931. typeOption,
  3932. typeKey,
  3933. nestedEditables,
  3934. visualOptions[key],
  3935. true
  3936. );
  3937. });
  3938. } else {
  3939. traverse(option, key, editables[type], visualOptions);
  3940. }
  3941. });
  3942. return visualOptions;
  3943. },
  3944. /**
  3945. * Get all class names for all parents in the element. Iterates until finds
  3946. * main container.
  3947. *
  3948. * @function Highcharts.NavigationBindings#getClickedClassNames
  3949. *
  3950. * @param {Highcharts.HTMLDOMElement}
  3951. * Container that event is bound to.
  3952. *
  3953. * @param {global.Event} event
  3954. * Browser's event.
  3955. *
  3956. * @return {Array<string>}
  3957. * Array of class names with corresponding elements
  3958. */
  3959. getClickedClassNames: function (container, event) {
  3960. var element = event.target,
  3961. classNames = [],
  3962. elemClassName;
  3963. while (element) {
  3964. elemClassName = attr(element, 'class');
  3965. if (elemClassName) {
  3966. classNames = classNames.concat(
  3967. elemClassName.split(' ').map(
  3968. function (name) { // eslint-disable-line no-loop-func
  3969. return [
  3970. name,
  3971. element
  3972. ];
  3973. }
  3974. )
  3975. );
  3976. }
  3977. element = element.parentNode;
  3978. if (element === container) {
  3979. return classNames;
  3980. }
  3981. }
  3982. return classNames;
  3983. },
  3984. /**
  3985. * Get events bound to a button. It's a custom event delegation to find all
  3986. * events connected to the element.
  3987. *
  3988. * @function Highcharts.NavigationBindings#getButtonEvents
  3989. *
  3990. * @param {Highcharts.HTMLDOMElement}
  3991. * Container that event is bound to.
  3992. *
  3993. * @param {global.Event} event
  3994. * Browser's event.
  3995. *
  3996. * @return {object}
  3997. * Oject with events (init, start, steps, and end)
  3998. */
  3999. getButtonEvents: function (container, event) {
  4000. var navigation = this,
  4001. classNames = this.getClickedClassNames(container, event),
  4002. bindings;
  4003. classNames.forEach(function (className) {
  4004. if (navigation.boundClassNames[className[0]] && !bindings) {
  4005. bindings = {
  4006. events: navigation.boundClassNames[className[0]],
  4007. button: className[1]
  4008. };
  4009. }
  4010. });
  4011. return bindings;
  4012. },
  4013. /**
  4014. * Bindings are just events, so the whole update process is simply
  4015. * removing old events and adding new ones.
  4016. *
  4017. * @private
  4018. * @function Highcharts.NavigationBindings#update
  4019. */
  4020. update: function (options) {
  4021. this.options = merge(true, this.options, options);
  4022. this.removeEvents();
  4023. this.initEvents();
  4024. },
  4025. /**
  4026. * Remove all events created in the navigation.
  4027. *
  4028. * @private
  4029. * @function Highcharts.NavigationBindings#removeEvents
  4030. */
  4031. removeEvents: function () {
  4032. this.eventsToUnbind.forEach(function (unbinder) {
  4033. unbinder();
  4034. });
  4035. },
  4036. destroy: function () {
  4037. this.removeEvents();
  4038. },
  4039. /**
  4040. * General utils for bindings
  4041. *
  4042. * @private
  4043. * @name Highcharts.NavigationBindings#utils
  4044. * @type {bindingsUtils}
  4045. */
  4046. utils: bindingsUtils
  4047. });
  4048. H.Chart.prototype.initNavigationBindings = function () {
  4049. var chart = this,
  4050. options = chart.options;
  4051. if (options && options.navigation && options.navigation.bindings) {
  4052. chart.navigationBindings = new H.NavigationBindings(
  4053. chart,
  4054. options.navigation
  4055. );
  4056. chart.navigationBindings.initEvents();
  4057. chart.navigationBindings.initUpdate();
  4058. }
  4059. };
  4060. addEvent(H.Chart, 'load', function () {
  4061. this.initNavigationBindings();
  4062. });
  4063. addEvent(H.Chart, 'destroy', function () {
  4064. if (this.navigationBindings) {
  4065. this.navigationBindings.destroy();
  4066. }
  4067. });
  4068. addEvent(H.NavigationBindings, 'deselectButton', function () {
  4069. this.selectedButtonElement = null;
  4070. });
  4071. addEvent(H.Annotation, 'remove', function () {
  4072. if (this.chart.navigationBindings) {
  4073. this.chart.navigationBindings.deselectAnnotation();
  4074. }
  4075. });
  4076. // Show edit-annotation form:
  4077. function selectableAnnotation(annotationType) {
  4078. var originalClick = annotationType.prototype.defaultOptions.events &&
  4079. annotationType.prototype.defaultOptions.events.click;
  4080. function selectAndshowPopup(event) {
  4081. var annotation = this,
  4082. navigation = annotation.chart.navigationBindings,
  4083. prevAnnotation = navigation.activeAnnotation;
  4084. if (originalClick) {
  4085. originalClick.click.call(annotation, event);
  4086. }
  4087. if (prevAnnotation !== annotation) {
  4088. // Select current:
  4089. navigation.deselectAnnotation();
  4090. navigation.activeAnnotation = annotation;
  4091. annotation.setControlPointsVisibility(true);
  4092. fireEvent(
  4093. navigation,
  4094. 'showPopup',
  4095. {
  4096. annotation: annotation,
  4097. formType: 'annotation-toolbar',
  4098. options: navigation.annotationToFields(annotation),
  4099. onSubmit: function (data) {
  4100. var config = {},
  4101. typeOptions;
  4102. if (data.actionType === 'remove') {
  4103. navigation.activeAnnotation = false;
  4104. navigation.chart.removeAnnotation(annotation);
  4105. } else {
  4106. navigation.fieldsToOptions(data.fields, config);
  4107. navigation.deselectAnnotation();
  4108. typeOptions = config.typeOptions;
  4109. if (annotation.options.type === 'measure') {
  4110. // Manually disable crooshars according to
  4111. // stroke width of the shape:
  4112. typeOptions.crosshairY.enabled =
  4113. typeOptions.crosshairY.strokeWidth !== 0;
  4114. typeOptions.crosshairX.enabled =
  4115. typeOptions.crosshairX.strokeWidth !== 0;
  4116. }
  4117. annotation.update(config);
  4118. }
  4119. }
  4120. }
  4121. );
  4122. } else {
  4123. // Deselect current:
  4124. navigation.deselectAnnotation();
  4125. fireEvent(navigation, 'closePopup');
  4126. }
  4127. // Let bubble event to chart.click:
  4128. event.activeAnnotation = true;
  4129. }
  4130. H.merge(
  4131. true,
  4132. annotationType.prototype.defaultOptions.events,
  4133. {
  4134. click: selectAndshowPopup
  4135. }
  4136. );
  4137. }
  4138. if (H.Annotation) {
  4139. // Basic shapes:
  4140. selectableAnnotation(H.Annotation);
  4141. // Advanced annotations:
  4142. objectEach(H.Annotation.types, function (annotationType) {
  4143. selectableAnnotation(annotationType);
  4144. });
  4145. }
  4146. H.setOptions({
  4147. /**
  4148. * @optionparent lang
  4149. */
  4150. lang: {
  4151. /**
  4152. * Configure the Popup strings in the chart. Requires the
  4153. * `annotations.js` or `annotations-advanced.src.js` module to be
  4154. * loaded.
  4155. *
  4156. * @since 7.0.0
  4157. * @type {Object}
  4158. * @product highcharts highstock
  4159. */
  4160. navigation: {
  4161. /**
  4162. * Translations for all field names used in popup.
  4163. *
  4164. * @product highcharts highstock
  4165. * @type {Object}
  4166. */
  4167. popup: {
  4168. simpleShapes: 'Simple shapes',
  4169. lines: 'Lines',
  4170. circle: 'Circle',
  4171. rectangle: 'Rectangle',
  4172. label: 'Label',
  4173. shapeOptions: 'Shape options',
  4174. typeOptions: 'Details',
  4175. fill: 'Fill',
  4176. format: 'Text',
  4177. strokeWidth: 'Line width',
  4178. stroke: 'Line color',
  4179. title: 'Title',
  4180. name: 'Name',
  4181. labelOptions: 'Label options',
  4182. labels: 'Labels',
  4183. backgroundColor: 'Background color',
  4184. backgroundColors: 'Background colors',
  4185. borderColor: 'Border color',
  4186. borderRadius: 'Border radius',
  4187. borderWidth: 'Border width',
  4188. style: 'Style',
  4189. padding: 'Padding',
  4190. fontSize: 'Font size',
  4191. color: 'Color',
  4192. height: 'Height',
  4193. shapes: 'Shape options'
  4194. }
  4195. }
  4196. },
  4197. /**
  4198. * @optionparent navigation
  4199. * @product highcharts highstock
  4200. */
  4201. navigation: {
  4202. /**
  4203. * A CSS class name where all bindings will be attached to. Multiple
  4204. * charts on the same page should have separate class names to prevent
  4205. * duplicating events.
  4206. *
  4207. * Default value of versions < 7.0.4 `highcharts-bindings-wrapper`
  4208. *
  4209. * @since 7.0.0
  4210. * @type {string}
  4211. */
  4212. bindingsClassName: 'highcharts-bindings-container',
  4213. /**
  4214. * Bindings definitions for custom HTML buttons. Each binding implements
  4215. * simple event-driven interface:
  4216. *
  4217. * - `className`: classname used to bind event to
  4218. *
  4219. * - `init`: initial event, fired on button click
  4220. *
  4221. * - `start`: fired on first click on a chart
  4222. *
  4223. * - `steps`: array of sequential events fired one after another on each
  4224. * of users clicks
  4225. *
  4226. * - `end`: last event to be called after last step event
  4227. *
  4228. * @type {Highcharts.Dictionary<Highcharts.StockToolsBindingsObject>|*}
  4229. * @sample stock/stocktools/stocktools-thresholds
  4230. * Custom bindings in Highstock
  4231. * @since 7.0.0
  4232. * @product highcharts highstock
  4233. */
  4234. bindings: {
  4235. /**
  4236. * A circle annotation bindings. Includes `start` and one event in
  4237. * `steps` array.
  4238. *
  4239. * @type {Highcharts.StockToolsBindingsObject}
  4240. * @default {"className": "highcharts-circle-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}}
  4241. */
  4242. circleAnnotation: {
  4243. /** @ignore */
  4244. className: 'highcharts-circle-annotation',
  4245. /** @ignore */
  4246. start: function (e) {
  4247. var coords = this.chart.pointer.getCoordinates(e),
  4248. navigation = this.chart.options.navigation,
  4249. controlPoints = [{
  4250. positioner: function (target) {
  4251. var xy = H.Annotation.MockPoint
  4252. .pointToPixels(
  4253. target.points[0]
  4254. ),
  4255. r = target.options.r;
  4256. return {
  4257. x: xy.x + r * Math.cos(Math.PI / 4) -
  4258. this.graphic.width / 2,
  4259. y: xy.y + r * Math.sin(Math.PI / 4) -
  4260. this.graphic.height / 2
  4261. };
  4262. },
  4263. events: {
  4264. // TRANSFORM RADIUS ACCORDING TO Y
  4265. // TRANSLATION
  4266. drag: function (e, target) {
  4267. var annotation = target.annotation,
  4268. position = this
  4269. .mouseMoveToTranslation(e);
  4270. target.setRadius(
  4271. Math.max(
  4272. target.options.r +
  4273. position.y /
  4274. Math.sin(Math.PI / 4),
  4275. 5
  4276. )
  4277. );
  4278. annotation.options.shapes[0] =
  4279. annotation.userOptions.shapes[0] =
  4280. target.options;
  4281. target.redraw(false);
  4282. }
  4283. }
  4284. }];
  4285. return this.chart.addAnnotation(
  4286. merge(
  4287. {
  4288. langKey: 'circle',
  4289. shapes: [{
  4290. type: 'circle',
  4291. point: {
  4292. xAxis: 0,
  4293. yAxis: 0,
  4294. x: coords.xAxis[0].value,
  4295. y: coords.yAxis[0].value
  4296. },
  4297. r: 5,
  4298. controlPoints: controlPoints
  4299. }]
  4300. },
  4301. navigation
  4302. .annotationsOptions,
  4303. navigation
  4304. .bindings
  4305. .circleAnnotation
  4306. .annotationsOptions
  4307. )
  4308. );
  4309. },
  4310. /** @ignore */
  4311. steps: [
  4312. function (e, annotation) {
  4313. var point = annotation.options.shapes[0].point,
  4314. x = this.chart.xAxis[0].toPixels(point.x),
  4315. y = this.chart.yAxis[0].toPixels(point.y),
  4316. inverted = this.chart.inverted,
  4317. distance = Math.max(
  4318. Math.sqrt(
  4319. Math.pow(
  4320. inverted ? y - e.chartX : x - e.chartX,
  4321. 2
  4322. ) +
  4323. Math.pow(
  4324. inverted ? x - e.chartY : y - e.chartY,
  4325. 2
  4326. )
  4327. ),
  4328. 5
  4329. );
  4330. annotation.update({
  4331. shapes: [{
  4332. r: distance
  4333. }]
  4334. });
  4335. }
  4336. ]
  4337. },
  4338. /**
  4339. * A rectangle annotation bindings. Includes `start` and one event
  4340. * in `steps` array.
  4341. *
  4342. * @type {Highcharts.StockToolsBindingsObject}
  4343. * @default {"className": "highcharts-rectangle-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}}
  4344. */
  4345. rectangleAnnotation: {
  4346. /** @ignore */
  4347. className: 'highcharts-rectangle-annotation',
  4348. /** @ignore */
  4349. start: function (e) {
  4350. var coords = this.chart.pointer.getCoordinates(e),
  4351. navigation = this.chart.options.navigation,
  4352. x = coords.xAxis[0].value,
  4353. y = coords.yAxis[0].value,
  4354. controlPoints = [{
  4355. positioner: function (annotation) {
  4356. var xy = H.Annotation.MockPoint
  4357. .pointToPixels(
  4358. annotation.shapes[0].points[2]
  4359. );
  4360. return {
  4361. x: xy.x - 4,
  4362. y: xy.y - 4
  4363. };
  4364. },
  4365. events: {
  4366. drag: function (e, target) {
  4367. var coords = this.chart.pointer
  4368. .getCoordinates(e),
  4369. x = coords.xAxis[0].value,
  4370. y = coords.yAxis[0].value,
  4371. shape = target.options.shapes[0],
  4372. points = shape.points;
  4373. // Top right point
  4374. points[1].x = x;
  4375. // Bottom right point (cursor position)
  4376. points[2].x = x;
  4377. points[2].y = y;
  4378. // Bottom left
  4379. points[3].y = y;
  4380. target.options.shapes[0].points = points;
  4381. target.redraw(false);
  4382. }
  4383. }
  4384. }];
  4385. return this.chart.addAnnotation(
  4386. merge(
  4387. {
  4388. langKey: 'rectangle',
  4389. shapes: [{
  4390. type: 'path',
  4391. points: [{
  4392. xAxis: 0,
  4393. yAxis: 0,
  4394. x: x,
  4395. y: y
  4396. }, {
  4397. xAxis: 0,
  4398. yAxis: 0,
  4399. x: x,
  4400. y: y
  4401. }, {
  4402. xAxis: 0,
  4403. yAxis: 0,
  4404. x: x,
  4405. y: y
  4406. }, {
  4407. xAxis: 0,
  4408. yAxis: 0,
  4409. x: x,
  4410. y: y
  4411. }]
  4412. }],
  4413. controlPoints: controlPoints
  4414. },
  4415. navigation
  4416. .annotationsOptions,
  4417. navigation
  4418. .bindings
  4419. .rectangleAnnotation
  4420. .annotationsOptions
  4421. )
  4422. );
  4423. },
  4424. /** @ignore */
  4425. steps: [
  4426. function (e, annotation) {
  4427. var points = annotation.options.shapes[0].points,
  4428. coords = this.chart.pointer.getCoordinates(e),
  4429. x = coords.xAxis[0].value,
  4430. y = coords.yAxis[0].value;
  4431. // Top right point
  4432. points[1].x = x;
  4433. // Bottom right point (cursor position)
  4434. points[2].x = x;
  4435. points[2].y = y;
  4436. // Bottom left
  4437. points[3].y = y;
  4438. annotation.update({
  4439. shapes: [{
  4440. points: points
  4441. }]
  4442. });
  4443. }
  4444. ]
  4445. },
  4446. /**
  4447. * A label annotation bindings. Includes `start` event only.
  4448. *
  4449. * @type {Highcharts.StockToolsBindingsObject}
  4450. * @default {"className": "highcharts-label-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}}
  4451. */
  4452. labelAnnotation: {
  4453. /** @ignore */
  4454. className: 'highcharts-label-annotation',
  4455. /** @ignore */
  4456. start: function (e) {
  4457. var coords = this.chart.pointer.getCoordinates(e),
  4458. navigation = this.chart.options.navigation,
  4459. controlPoints = [{
  4460. symbol: 'triangle-down',
  4461. positioner: function (target) {
  4462. if (!target.graphic.placed) {
  4463. return {
  4464. x: 0,
  4465. y: -9e7
  4466. };
  4467. }
  4468. var xy = H.Annotation.MockPoint
  4469. .pointToPixels(
  4470. target.points[0]
  4471. );
  4472. return {
  4473. x: xy.x - this.graphic.width / 2,
  4474. y: xy.y - this.graphic.height / 2
  4475. };
  4476. },
  4477. // TRANSLATE POINT/ANCHOR
  4478. events: {
  4479. drag: function (e, target) {
  4480. var xy = this.mouseMoveToTranslation(e);
  4481. target.translatePoint(xy.x, xy.y);
  4482. target.annotation.labels[0].options =
  4483. target.options;
  4484. target.redraw(false);
  4485. }
  4486. }
  4487. }, {
  4488. symbol: 'square',
  4489. positioner: function (target) {
  4490. if (!target.graphic.placed) {
  4491. return {
  4492. x: 0,
  4493. y: -9e7
  4494. };
  4495. }
  4496. return {
  4497. x: target.graphic.alignAttr.x -
  4498. this.graphic.width / 2,
  4499. y: target.graphic.alignAttr.y -
  4500. this.graphic.height / 2
  4501. };
  4502. },
  4503. // TRANSLATE POSITION WITHOUT CHANGING THE
  4504. // ANCHOR
  4505. events: {
  4506. drag: function (e, target) {
  4507. var xy = this.mouseMoveToTranslation(e);
  4508. target.translate(xy.x, xy.y);
  4509. target.annotation.labels[0].options =
  4510. target.options;
  4511. target.redraw(false);
  4512. }
  4513. }
  4514. }];
  4515. return this.chart.addAnnotation(
  4516. merge(
  4517. {
  4518. langKey: 'label',
  4519. labelOptions: {
  4520. format: '{y:.2f}'
  4521. },
  4522. labels: [{
  4523. point: {
  4524. xAxis: 0,
  4525. yAxis: 0,
  4526. x: coords.xAxis[0].value,
  4527. y: coords.yAxis[0].value
  4528. },
  4529. overflow: 'none',
  4530. crop: true,
  4531. controlPoints: controlPoints
  4532. }]
  4533. },
  4534. navigation
  4535. .annotationsOptions,
  4536. navigation
  4537. .bindings
  4538. .labelAnnotation
  4539. .annotationsOptions
  4540. )
  4541. );
  4542. }
  4543. }
  4544. },
  4545. /**
  4546. * Path where Highcharts will look for icons. Change this to use icons
  4547. * from a different server.
  4548. *
  4549. * @type {string}
  4550. * @default https://code.highcharts.com/8.0.0/gfx/stock-icons/
  4551. * @since 7.1.3
  4552. * @apioption navigation.iconsURL
  4553. */
  4554. /**
  4555. * A `showPopup` event. Fired when selecting for example an annotation.
  4556. *
  4557. * @type {Function}
  4558. * @apioption navigation.events.showPopup
  4559. */
  4560. /**
  4561. * A `closePopup` event. Fired when Popup should be hidden, for example
  4562. * when clicking on an annotation again.
  4563. *
  4564. * @type {Function}
  4565. * @apioption navigation.events.closePopup
  4566. */
  4567. /**
  4568. * Event fired on a button click.
  4569. *
  4570. * @type {Function}
  4571. * @sample highcharts/annotations/gui/
  4572. * Change icon in a dropddown on event
  4573. * @sample highcharts/annotations/gui-buttons/
  4574. * Change button class on event
  4575. * @apioption navigation.events.selectButton
  4576. */
  4577. /**
  4578. * Event fired when button state should change, for example after
  4579. * adding an annotation.
  4580. *
  4581. * @type {Function}
  4582. * @sample highcharts/annotations/gui/
  4583. * Change icon in a dropddown on event
  4584. * @sample highcharts/annotations/gui-buttons/
  4585. * Change button class on event
  4586. * @apioption navigation.events.deselectButton
  4587. */
  4588. /**
  4589. * Events to communicate between Stock Tools and custom GUI.
  4590. *
  4591. * @since 7.0.0
  4592. * @product highcharts highstock
  4593. * @optionparent navigation.events
  4594. */
  4595. events: {},
  4596. /**
  4597. * Additional options to be merged into all annotations.
  4598. *
  4599. * @sample stock/stocktools/navigation-annotation-options
  4600. * Set red color of all line annotations
  4601. *
  4602. * @type {Highcharts.AnnotationsOptions}
  4603. * @extends annotations
  4604. * @exclude crookedLine, elliottWave, fibonacci, infinityLine,
  4605. * measure, pitchfork, tunnel, verticalLine
  4606. * @apioption navigation.annotationsOptions
  4607. */
  4608. annotationsOptions: {}
  4609. }
  4610. });
  4611. });
  4612. _registerModule(_modules, 'annotations/popup.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
  4613. /* *
  4614. *
  4615. * Popup generator for Stock tools
  4616. *
  4617. * (c) 2009-2017 Sebastian Bochan
  4618. *
  4619. * License: www.highcharts.com/license
  4620. *
  4621. * */
  4622. var defined = U.defined,
  4623. isArray = U.isArray,
  4624. isObject = U.isObject,
  4625. isString = U.isString,
  4626. objectEach = U.objectEach,
  4627. pick = U.pick,
  4628. wrap = U.wrap;
  4629. var addEvent = H.addEvent,
  4630. createElement = H.createElement,
  4631. indexFilter = /\d/g,
  4632. PREFIX = 'highcharts-',
  4633. DIV = 'div',
  4634. INPUT = 'input',
  4635. LABEL = 'label',
  4636. BUTTON = 'button',
  4637. SELECT = 'select',
  4638. OPTION = 'option',
  4639. SPAN = 'span',
  4640. UL = 'ul',
  4641. LI = 'li',
  4642. H3 = 'h3';
  4643. // onContainerMouseDown blocks internal popup events, due to e.preventDefault.
  4644. // Related issue #4606
  4645. wrap(H.Pointer.prototype, 'onContainerMouseDown', function (proceed, e) {
  4646. var popupClass = e.target && e.target.className;
  4647. // elements is not in popup
  4648. if (!(isString(popupClass) &&
  4649. popupClass.indexOf(PREFIX + 'popup-field') >= 0)
  4650. ) {
  4651. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  4652. }
  4653. });
  4654. H.Popup = function (parentDiv, iconsURL) {
  4655. this.init(parentDiv, iconsURL);
  4656. };
  4657. H.Popup.prototype = {
  4658. /**
  4659. * Initialize the popup. Create base div and add close button.
  4660. * @private
  4661. * @param {HTMLDOMElement} - container where popup should be placed
  4662. * @param {Object} - user options
  4663. * @return {HTMLDOMElement} - return created popup's div
  4664. */
  4665. init: function (parentDiv, iconsURL) {
  4666. // create popup div
  4667. this.container = createElement(DIV, {
  4668. className: PREFIX + 'popup'
  4669. }, null, parentDiv);
  4670. this.lang = this.getLangpack();
  4671. this.iconsURL = iconsURL;
  4672. // add close button
  4673. this.addCloseBtn();
  4674. },
  4675. /**
  4676. * Create HTML element and attach click event (close popup).
  4677. * @private
  4678. */
  4679. addCloseBtn: function () {
  4680. var _self = this,
  4681. closeBtn;
  4682. // create close popup btn
  4683. closeBtn = createElement(DIV, {
  4684. className: PREFIX + 'popup-close'
  4685. }, null, this.container);
  4686. closeBtn.style['background-image'] = 'url(' +
  4687. this.iconsURL + 'close.svg)';
  4688. ['click', 'touchstart'].forEach(function (eventName) {
  4689. addEvent(closeBtn, eventName, function () {
  4690. _self.closePopup();
  4691. });
  4692. });
  4693. },
  4694. /**
  4695. * Create two columns (divs) in HTML.
  4696. * @private
  4697. * @param {HTMLDOMElement} - container of columns
  4698. * @return {Object} - reference to two HTML columns
  4699. */
  4700. addColsContainer: function (container) {
  4701. var rhsCol,
  4702. lhsCol;
  4703. // left column
  4704. lhsCol = createElement(DIV, {
  4705. className: PREFIX + 'popup-lhs-col'
  4706. }, null, container);
  4707. // right column
  4708. rhsCol = createElement(DIV, {
  4709. className: PREFIX + 'popup-rhs-col'
  4710. }, null, container);
  4711. // wrapper content
  4712. createElement(DIV, {
  4713. className: PREFIX + 'popup-rhs-col-wrapper'
  4714. }, null, rhsCol);
  4715. return {
  4716. lhsCol: lhsCol,
  4717. rhsCol: rhsCol
  4718. };
  4719. },
  4720. /**
  4721. * Create input with label.
  4722. * @private
  4723. * @param {String} - chain of fields i.e params.styles.fontSize
  4724. * @param {String} - indicator type
  4725. * @param {HTMLDOMElement} - container where elements should be added
  4726. * @param {String} - dafault value of input i.e period value is 14,
  4727. * extracted from defaultOptions (ADD mode) or series options (EDIT mode)
  4728. */
  4729. addInput: function (option, type, parentDiv, value) {
  4730. var optionParamList = option.split('.'),
  4731. optionName = optionParamList[optionParamList.length - 1],
  4732. lang = this.lang,
  4733. inputName = PREFIX + type + '-' + optionName;
  4734. if (!inputName.match(indexFilter)) {
  4735. // add label
  4736. createElement(
  4737. LABEL, {
  4738. innerHTML: lang[optionName] || optionName,
  4739. htmlFor: inputName
  4740. },
  4741. null,
  4742. parentDiv
  4743. );
  4744. }
  4745. // add input
  4746. createElement(
  4747. INPUT,
  4748. {
  4749. name: inputName,
  4750. value: value[0],
  4751. type: value[1],
  4752. className: PREFIX + 'popup-field'
  4753. },
  4754. null,
  4755. parentDiv
  4756. ).setAttribute(PREFIX + 'data-name', option);
  4757. },
  4758. /**
  4759. * Create button.
  4760. * @private
  4761. * @param {HTMLDOMElement} - container where elements should be added
  4762. * @param {String} - text placed as button label
  4763. * @param {String} - add | edit | remove
  4764. * @param {Function} - on click callback
  4765. * @param {HTMLDOMElement} - container where inputs are generated
  4766. * @return {HTMLDOMElement} - html button
  4767. */
  4768. addButton: function (parentDiv, label, type, callback, fieldsDiv) {
  4769. var _self = this,
  4770. closePopup = this.closePopup,
  4771. getFields = this.getFields,
  4772. button;
  4773. button = createElement(BUTTON, {
  4774. innerHTML: label
  4775. }, null, parentDiv);
  4776. ['click', 'touchstart'].forEach(function (eventName) {
  4777. addEvent(button, eventName, function () {
  4778. closePopup.call(_self);
  4779. return callback(
  4780. getFields(fieldsDiv, type)
  4781. );
  4782. });
  4783. });
  4784. return button;
  4785. },
  4786. /**
  4787. * Get values from all inputs and create JSON.
  4788. * @private
  4789. * @param {HTMLDOMElement} - container where inputs are created
  4790. * @param {String} - add | edit | remove
  4791. * @return {Object} - fields
  4792. */
  4793. getFields: function (parentDiv, type) {
  4794. var inputList = parentDiv.querySelectorAll('input'),
  4795. optionSeries = '#' + PREFIX + 'select-series > option:checked',
  4796. optionVolume = '#' + PREFIX + 'select-volume > option:checked',
  4797. linkedTo = parentDiv.querySelectorAll(optionSeries)[0],
  4798. volumeTo = parentDiv.querySelectorAll(optionVolume)[0],
  4799. seriesId,
  4800. param,
  4801. fieldsOutput;
  4802. fieldsOutput = {
  4803. actionType: type,
  4804. linkedTo: linkedTo && linkedTo.getAttribute('value'),
  4805. fields: { }
  4806. };
  4807. [].forEach.call(inputList, function (input) {
  4808. param = input.getAttribute(PREFIX + 'data-name');
  4809. seriesId = input.getAttribute(PREFIX + 'data-series-id');
  4810. // params
  4811. if (seriesId) {
  4812. fieldsOutput.seriesId = input.value;
  4813. } else if (param) {
  4814. fieldsOutput.fields[param] = input.value;
  4815. } else {
  4816. // type like sma / ema
  4817. fieldsOutput.type = input.value;
  4818. }
  4819. });
  4820. if (volumeTo) {
  4821. fieldsOutput.fields['params.volumeSeriesID'] = volumeTo
  4822. .getAttribute('value');
  4823. }
  4824. return fieldsOutput;
  4825. },
  4826. /**
  4827. * Reset content of the current popup and show.
  4828. * @private
  4829. * @param {Chart} - chart
  4830. * @param {Function} - on click callback
  4831. * @return {Object} - fields
  4832. */
  4833. showPopup: function () {
  4834. var popupDiv = this.container,
  4835. toolbarClass = PREFIX + 'annotation-toolbar',
  4836. popupCloseBtn = popupDiv
  4837. .querySelectorAll('.' + PREFIX + 'popup-close')[0];
  4838. // reset content
  4839. popupDiv.innerHTML = '';
  4840. // reset toolbar styles if exists
  4841. if (popupDiv.className.indexOf(toolbarClass) >= 0) {
  4842. popupDiv.classList.remove(toolbarClass);
  4843. // reset toolbar inline styles
  4844. popupDiv.removeAttribute('style');
  4845. }
  4846. // add close button
  4847. popupDiv.appendChild(popupCloseBtn);
  4848. popupDiv.style.display = 'block';
  4849. },
  4850. /**
  4851. * Hide popup.
  4852. * @private
  4853. */
  4854. closePopup: function () {
  4855. this.popup.container.style.display = 'none';
  4856. },
  4857. /**
  4858. * Create content and show popup.
  4859. * @private
  4860. * @param {String} - type of popup i.e indicators
  4861. * @param {Chart} - chart
  4862. * @param {Object} - options
  4863. * @param {Function} - on click callback
  4864. */
  4865. showForm: function (type, chart, options, callback) {
  4866. this.popup = chart.navigationBindings.popup;
  4867. // show blank popup
  4868. this.showPopup();
  4869. // indicator form
  4870. if (type === 'indicators') {
  4871. this.indicators.addForm.call(this, chart, options, callback);
  4872. }
  4873. // annotation small toolbar
  4874. if (type === 'annotation-toolbar') {
  4875. this.annotations.addToolbar.call(this, chart, options, callback);
  4876. }
  4877. // annotation edit form
  4878. if (type === 'annotation-edit') {
  4879. this.annotations.addForm.call(this, chart, options, callback);
  4880. }
  4881. // flags form - add / edit
  4882. if (type === 'flag') {
  4883. this.annotations.addForm.call(this, chart, options, callback, true);
  4884. }
  4885. },
  4886. /**
  4887. * Return lang definitions for popup.
  4888. * @private
  4889. * @return {Object} - elements translations.
  4890. */
  4891. getLangpack: function () {
  4892. return H.getOptions().lang.navigation.popup;
  4893. },
  4894. annotations: {
  4895. /**
  4896. * Create annotation simple form. It contains two buttons
  4897. * (edit / remove) and text label.
  4898. * @private
  4899. * @param {Chart} - chart
  4900. * @param {Object} - options
  4901. * @param {Function} - on click callback
  4902. */
  4903. addToolbar: function (chart, options, callback) {
  4904. var _self = this,
  4905. lang = this.lang,
  4906. popupDiv = this.popup.container,
  4907. showForm = this.showForm,
  4908. toolbarClass = PREFIX + 'annotation-toolbar',
  4909. button;
  4910. // set small size
  4911. if (popupDiv.className.indexOf(toolbarClass) === -1) {
  4912. popupDiv.className += ' ' + toolbarClass;
  4913. }
  4914. // set position
  4915. popupDiv.style.top = chart.plotTop + 10 + 'px';
  4916. // create label
  4917. createElement(SPAN, {
  4918. innerHTML: pick(
  4919. // Advanced annotations:
  4920. lang[options.langKey] || options.langKey,
  4921. // Basic shapes:
  4922. options.shapes && options.shapes[0].type
  4923. )
  4924. }, null, popupDiv);
  4925. // add buttons
  4926. button = this.addButton(
  4927. popupDiv,
  4928. lang.removeButton || 'remove',
  4929. 'remove',
  4930. callback,
  4931. popupDiv
  4932. );
  4933. button.className += ' ' + PREFIX + 'annotation-remove-button';
  4934. button.style['background-image'] = 'url(' +
  4935. this.iconsURL + 'destroy.svg)';
  4936. button = this.addButton(
  4937. popupDiv,
  4938. lang.editButton || 'edit',
  4939. 'edit',
  4940. function () {
  4941. showForm.call(
  4942. _self,
  4943. 'annotation-edit',
  4944. chart,
  4945. options,
  4946. callback
  4947. );
  4948. },
  4949. popupDiv
  4950. );
  4951. button.className += ' ' + PREFIX + 'annotation-edit-button';
  4952. button.style['background-image'] = 'url(' +
  4953. this.iconsURL + 'edit.svg)';
  4954. },
  4955. /**
  4956. * Create annotation simple form.
  4957. * It contains fields with param names.
  4958. * @private
  4959. * @param {Chart} - chart
  4960. * @param {Object} - options
  4961. * @param {Function} - on click callback
  4962. * @param {Boolean} - if it is a form declared for init annotation
  4963. */
  4964. addForm: function (chart, options, callback, isInit) {
  4965. var popupDiv = this.popup.container,
  4966. lang = this.lang,
  4967. bottomRow,
  4968. lhsCol;
  4969. // create title of annotations
  4970. lhsCol = createElement('h2', {
  4971. innerHTML: lang[options.langKey] || options.langKey,
  4972. className: PREFIX + 'popup-main-title'
  4973. }, null, popupDiv);
  4974. // left column
  4975. lhsCol = createElement(DIV, {
  4976. className: PREFIX + 'popup-lhs-col ' + PREFIX + 'popup-lhs-full'
  4977. }, null, popupDiv);
  4978. bottomRow = createElement(DIV, {
  4979. className: PREFIX + 'popup-bottom-row'
  4980. }, null, popupDiv);
  4981. this.annotations.addFormFields.call(
  4982. this,
  4983. lhsCol,
  4984. chart,
  4985. '',
  4986. options,
  4987. [],
  4988. true
  4989. );
  4990. this.addButton(
  4991. bottomRow,
  4992. isInit ?
  4993. (lang.addButton || 'add') :
  4994. (lang.saveButton || 'save'),
  4995. isInit ? 'add' : 'save',
  4996. callback,
  4997. popupDiv
  4998. );
  4999. },
  5000. /**
  5001. * Create annotation's form fields.
  5002. * @private
  5003. * @param {HTMLDOMElement} - div where inputs are placed
  5004. * @param {Chart} - chart
  5005. * @param {String} - name of parent to create chain of names
  5006. * @param {Object} - options
  5007. * @param {Array} - storage - array where all items are stored
  5008. * @param {Boolean} - isRoot - recursive flag for root
  5009. */
  5010. addFormFields: function (
  5011. parentDiv,
  5012. chart,
  5013. parentNode,
  5014. options,
  5015. storage,
  5016. isRoot
  5017. ) {
  5018. var _self = this,
  5019. addFormFields = this.annotations.addFormFields,
  5020. addInput = this.addInput,
  5021. lang = this.lang,
  5022. parentFullName,
  5023. titleName;
  5024. objectEach(options, function (value, option) {
  5025. // create name like params.styles.fontSize
  5026. parentFullName = parentNode !== '' ?
  5027. parentNode + '.' + option : option;
  5028. if (isObject(value)) {
  5029. if (
  5030. // value is object of options
  5031. !isArray(value) ||
  5032. // array of objects with params. i.e labels in Fibonacci
  5033. (isArray(value) && isObject(value[0]))
  5034. ) {
  5035. titleName = lang[option] || option;
  5036. if (!titleName.match(indexFilter)) {
  5037. storage.push([
  5038. true,
  5039. titleName,
  5040. parentDiv
  5041. ]);
  5042. }
  5043. addFormFields.call(
  5044. _self,
  5045. parentDiv,
  5046. chart,
  5047. parentFullName,
  5048. value,
  5049. storage,
  5050. false
  5051. );
  5052. } else {
  5053. storage.push([
  5054. _self,
  5055. parentFullName,
  5056. 'annotation',
  5057. parentDiv,
  5058. value
  5059. ]);
  5060. }
  5061. }
  5062. });
  5063. if (isRoot) {
  5064. storage = storage.sort(function (a) {
  5065. return a[1].match(/format/g) ? -1 : 1;
  5066. });
  5067. storage.forEach(function (genInput) {
  5068. if (genInput[0] === true) {
  5069. createElement(SPAN, {
  5070. className: PREFIX + 'annotation-title',
  5071. innerHTML: genInput[1]
  5072. }, null, genInput[2]);
  5073. } else {
  5074. addInput.apply(genInput[0], genInput.splice(1));
  5075. }
  5076. });
  5077. }
  5078. }
  5079. },
  5080. indicators: {
  5081. /**
  5082. * Create indicator's form. It contains two tabs (ADD and EDIT) with
  5083. * content.
  5084. * @private
  5085. * @param {Chart} - chart
  5086. * @param {Object} - options
  5087. * @param {Function} - on click callback
  5088. */
  5089. addForm: function (chart, options, callback) {
  5090. var tabsContainers,
  5091. indicators = this.indicators,
  5092. lang = this.lang,
  5093. buttonParentDiv;
  5094. // add tabs
  5095. this.tabs.init.call(this, chart);
  5096. // get all tabs content divs
  5097. tabsContainers = this.popup.container
  5098. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  5099. // ADD tab
  5100. this.addColsContainer(tabsContainers[0]);
  5101. indicators.addIndicatorList.call(
  5102. this,
  5103. chart,
  5104. tabsContainers[0],
  5105. 'add'
  5106. );
  5107. buttonParentDiv = tabsContainers[0]
  5108. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  5109. this.addButton(
  5110. buttonParentDiv,
  5111. lang.addButton || 'add',
  5112. 'add',
  5113. callback,
  5114. buttonParentDiv
  5115. );
  5116. // EDIT tab
  5117. this.addColsContainer(tabsContainers[1]);
  5118. indicators.addIndicatorList.call(
  5119. this,
  5120. chart,
  5121. tabsContainers[1],
  5122. 'edit'
  5123. );
  5124. buttonParentDiv = tabsContainers[1]
  5125. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  5126. this.addButton(
  5127. buttonParentDiv,
  5128. lang.saveButton || 'save',
  5129. 'edit',
  5130. callback,
  5131. buttonParentDiv
  5132. );
  5133. this.addButton(
  5134. buttonParentDiv,
  5135. lang.removeButton || 'remove',
  5136. 'remove',
  5137. callback,
  5138. buttonParentDiv
  5139. );
  5140. },
  5141. /**
  5142. * Create HTML list of all indicators (ADD mode) or added indicators
  5143. * (EDIT mode).
  5144. * @private
  5145. * @param {Chart} - chart
  5146. * @param {HTMLDOMElement} - container where list is added
  5147. * @param {String} - 'edit' or 'add' mode
  5148. */
  5149. addIndicatorList: function (chart, parentDiv, listType) {
  5150. var _self = this,
  5151. lhsCol = parentDiv
  5152. .querySelectorAll('.' + PREFIX + 'popup-lhs-col')[0],
  5153. rhsCol = parentDiv
  5154. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0],
  5155. isEdit = listType === 'edit',
  5156. series = isEdit ? chart.series : // EDIT mode
  5157. chart.options.plotOptions, // ADD mode
  5158. addFormFields = this.indicators.addFormFields,
  5159. rhsColWrapper,
  5160. indicatorList,
  5161. item;
  5162. // create wrapper for list
  5163. indicatorList = createElement(UL, {
  5164. className: PREFIX + 'indicator-list'
  5165. }, null, lhsCol);
  5166. rhsColWrapper = rhsCol
  5167. .querySelectorAll('.' + PREFIX + 'popup-rhs-col-wrapper')[0];
  5168. objectEach(series, function (serie, value) {
  5169. var seriesOptions = serie.options;
  5170. if (
  5171. serie.params ||
  5172. seriesOptions && seriesOptions.params
  5173. ) {
  5174. var indicatorNameType = _self.indicators
  5175. .getNameType(serie, value),
  5176. indicatorType = indicatorNameType.type;
  5177. item = createElement(LI, {
  5178. className: PREFIX + 'indicator-list',
  5179. innerHTML: indicatorNameType.name
  5180. }, null, indicatorList);
  5181. ['click', 'touchstart'].forEach(function (eventName) {
  5182. addEvent(item, eventName, function () {
  5183. addFormFields.call(
  5184. _self,
  5185. chart,
  5186. isEdit ? serie : series[indicatorType],
  5187. indicatorNameType.type,
  5188. rhsColWrapper
  5189. );
  5190. // add hidden input with series.id
  5191. if (isEdit && serie.options) {
  5192. createElement(INPUT, {
  5193. type: 'hidden',
  5194. name: PREFIX + 'id-' + indicatorType,
  5195. value: serie.options.id
  5196. }, null, rhsColWrapper)
  5197. .setAttribute(
  5198. PREFIX + 'data-series-id',
  5199. serie.options.id
  5200. );
  5201. }
  5202. });
  5203. });
  5204. }
  5205. });
  5206. // select first item from the list
  5207. if (indicatorList.childNodes.length > 0) {
  5208. indicatorList.childNodes[0].click();
  5209. }
  5210. },
  5211. /**
  5212. * Extract full name and type of requested indicator.
  5213. * @private
  5214. * @param {Series} - series which name is needed.
  5215. * (EDIT mode - defaultOptions.series, ADD mode - indicator series).
  5216. * @param {String} - indicator type like: sma, ema, etc.
  5217. * @return {Object} - series name and type like: sma, ema, etc.
  5218. */
  5219. getNameType: function (series, type) {
  5220. var options = series.options,
  5221. seriesTypes = H.seriesTypes,
  5222. // add mode
  5223. seriesName = seriesTypes[type] &&
  5224. seriesTypes[type].prototype.nameBase || type.toUpperCase(),
  5225. seriesType = type;
  5226. // edit
  5227. if (options && options.type) {
  5228. seriesType = series.options.type;
  5229. seriesName = series.name;
  5230. }
  5231. return {
  5232. name: seriesName,
  5233. type: seriesType
  5234. };
  5235. },
  5236. /**
  5237. * List all series with unique ID. Its mandatory for indicators to set
  5238. * correct linking.
  5239. * @private
  5240. * @param {String} - indicator type like: sma, ema, etc.
  5241. * @param {String} - type of select i.e series or volume.
  5242. * @param {Chart} - chart
  5243. * @param {HTMLDOMElement} - element where created HTML list is added
  5244. * @param {String} selectedOption
  5245. * optional param for default value in dropdown
  5246. */
  5247. listAllSeries: function (
  5248. type,
  5249. optionName,
  5250. chart,
  5251. parentDiv,
  5252. selectedOption
  5253. ) {
  5254. var selectName = PREFIX + optionName + '-type-' + type,
  5255. lang = this.lang,
  5256. selectBox,
  5257. seriesOptions;
  5258. createElement(
  5259. LABEL, {
  5260. innerHTML: lang[optionName] || optionName,
  5261. htmlFor: selectName
  5262. },
  5263. null,
  5264. parentDiv
  5265. );
  5266. // select type
  5267. selectBox = createElement(
  5268. SELECT,
  5269. {
  5270. name: selectName,
  5271. className: PREFIX + 'popup-field'
  5272. },
  5273. null,
  5274. parentDiv
  5275. );
  5276. selectBox.setAttribute('id', PREFIX + 'select-' + optionName);
  5277. // list all series which have id - mandatory for creating indicator
  5278. chart.series.forEach(function (serie) {
  5279. seriesOptions = serie.options;
  5280. if (
  5281. !seriesOptions.params &&
  5282. seriesOptions.id &&
  5283. seriesOptions.id !== PREFIX + 'navigator-series'
  5284. ) {
  5285. createElement(
  5286. OPTION,
  5287. {
  5288. innerHTML: seriesOptions.name || seriesOptions.id,
  5289. value: seriesOptions.id
  5290. },
  5291. null,
  5292. selectBox
  5293. );
  5294. }
  5295. });
  5296. if (defined(selectedOption)) {
  5297. selectBox.value = selectedOption;
  5298. }
  5299. },
  5300. /**
  5301. * Create typical inputs for chosen indicator. Fields are extracted from
  5302. * defaultOptions (ADD mode) or current indicator (ADD mode). Two extra
  5303. * fields are added:
  5304. * - hidden input - contains indicator type (required for callback)
  5305. * - select - list of series which can be linked with indicator
  5306. * @private
  5307. * @param {Chart} - chart
  5308. * @param {Series} - indicator
  5309. * @param {String} - indicator type like: sma, ema, etc.
  5310. * @param {HTMLDOMElement} - element where created HTML list is added
  5311. */
  5312. addFormFields: function (chart, series, seriesType, rhsColWrapper) {
  5313. var fields = series.params || series.options.params,
  5314. getNameType = this.indicators.getNameType;
  5315. // reset current content
  5316. rhsColWrapper.innerHTML = '';
  5317. // create title (indicator name in the right column)
  5318. createElement(
  5319. H3,
  5320. {
  5321. className: PREFIX + 'indicator-title',
  5322. innerHTML: getNameType(series, seriesType).name
  5323. },
  5324. null,
  5325. rhsColWrapper
  5326. );
  5327. // input type
  5328. createElement(
  5329. INPUT,
  5330. {
  5331. type: 'hidden',
  5332. name: PREFIX + 'type-' + seriesType,
  5333. value: seriesType
  5334. },
  5335. null,
  5336. rhsColWrapper
  5337. );
  5338. // list all series with id
  5339. this.indicators.listAllSeries.call(
  5340. this,
  5341. seriesType,
  5342. 'series',
  5343. chart,
  5344. rhsColWrapper,
  5345. series.linkedParent && fields.volumeSeriesID
  5346. );
  5347. if (fields.volumeSeriesID) {
  5348. this.indicators.listAllSeries.call(
  5349. this,
  5350. seriesType,
  5351. 'volume',
  5352. chart,
  5353. rhsColWrapper,
  5354. series.linkedParent && series.linkedParent.options.id
  5355. );
  5356. }
  5357. // add param fields
  5358. this.indicators.addParamInputs.call(
  5359. this,
  5360. chart,
  5361. 'params',
  5362. fields,
  5363. seriesType,
  5364. rhsColWrapper
  5365. );
  5366. },
  5367. /**
  5368. * Recurent function which lists all fields, from params object and
  5369. * create them as inputs. Each input has unique `data-name` attribute,
  5370. * which keeps chain of fields i.e params.styles.fontSize.
  5371. * @private
  5372. * @param {Chart} - chart
  5373. * @param {String} - name of parent to create chain of names
  5374. * @param {Series} - fields - params which are based for input create
  5375. * @param {String} - indicator type like: sma, ema, etc.
  5376. * @param {HTMLDOMElement} - element where created HTML list is added
  5377. */
  5378. addParamInputs: function (chart, parentNode, fields, type, parentDiv) {
  5379. var _self = this,
  5380. addParamInputs = this.indicators.addParamInputs,
  5381. addInput = this.addInput,
  5382. parentFullName;
  5383. objectEach(fields, function (value, fieldName) {
  5384. // create name like params.styles.fontSize
  5385. parentFullName = parentNode + '.' + fieldName;
  5386. if (isObject(value)) {
  5387. addParamInputs.call(
  5388. _self,
  5389. chart,
  5390. parentFullName,
  5391. value,
  5392. type,
  5393. parentDiv
  5394. );
  5395. } else if (
  5396. // skip volume field which is created by addFormFields
  5397. parentFullName !== 'params.volumeSeriesID'
  5398. ) {
  5399. addInput.call(
  5400. _self,
  5401. parentFullName,
  5402. type,
  5403. parentDiv,
  5404. [value, 'text'] // all inputs are text type
  5405. );
  5406. }
  5407. });
  5408. },
  5409. /**
  5410. * Get amount of indicators added to chart.
  5411. * @private
  5412. * @return {Number} - Amount of indicators
  5413. */
  5414. getAmount: function () {
  5415. var series = this.series,
  5416. counter = 0;
  5417. objectEach(series, function (serie) {
  5418. var seriesOptions = serie.options;
  5419. if (
  5420. serie.params ||
  5421. seriesOptions && seriesOptions.params
  5422. ) {
  5423. counter++;
  5424. }
  5425. });
  5426. return counter;
  5427. }
  5428. },
  5429. tabs: {
  5430. /**
  5431. * Init tabs. Create tab menu items, tabs containers
  5432. * @private
  5433. * @param {Chart} - reference to current chart
  5434. */
  5435. init: function (chart) {
  5436. var tabs = this.tabs,
  5437. indicatorsCount = this.indicators.getAmount.call(chart),
  5438. firstTab; // run by default
  5439. // create menu items
  5440. firstTab = tabs.addMenuItem.call(this, 'add');
  5441. tabs.addMenuItem.call(this, 'edit', indicatorsCount);
  5442. // create tabs containers
  5443. tabs.addContentItem.call(this, 'add');
  5444. tabs.addContentItem.call(this, 'edit');
  5445. tabs.switchTabs.call(this, indicatorsCount);
  5446. // activate first tab
  5447. tabs.selectTab.call(this, firstTab, 0);
  5448. },
  5449. /**
  5450. * Create tab menu item
  5451. * @private
  5452. * @param {String} - `add` or `edit`
  5453. * @param {Number} - Disable tab when 0
  5454. * @return {HTMLDOMElement} - created HTML tab-menu element
  5455. */
  5456. addMenuItem: function (tabName, disableTab) {
  5457. var popupDiv = this.popup.container,
  5458. className = PREFIX + 'tab-item',
  5459. lang = this.lang,
  5460. menuItem;
  5461. if (disableTab === 0) {
  5462. className += ' ' + PREFIX + 'tab-disabled';
  5463. }
  5464. // tab 1
  5465. menuItem = createElement(
  5466. SPAN,
  5467. {
  5468. innerHTML: lang[tabName + 'Button'] || tabName,
  5469. className: className
  5470. },
  5471. null,
  5472. popupDiv
  5473. );
  5474. menuItem.setAttribute(PREFIX + 'data-tab-type', tabName);
  5475. return menuItem;
  5476. },
  5477. /**
  5478. * Create tab content
  5479. * @private
  5480. * @return {HTMLDOMElement} - created HTML tab-content element
  5481. */
  5482. addContentItem: function () {
  5483. var popupDiv = this.popup.container;
  5484. return createElement(
  5485. DIV,
  5486. {
  5487. className: PREFIX + 'tab-item-content'
  5488. },
  5489. null,
  5490. popupDiv
  5491. );
  5492. },
  5493. /**
  5494. * Add click event to each tab
  5495. * @private
  5496. * @param {Number} - Disable tab when 0
  5497. */
  5498. switchTabs: function (disableTab) {
  5499. var _self = this,
  5500. popupDiv = this.popup.container,
  5501. tabs = popupDiv.querySelectorAll('.' + PREFIX + 'tab-item'),
  5502. dataParam;
  5503. tabs.forEach(function (tab, i) {
  5504. dataParam = tab.getAttribute(PREFIX + 'data-tab-type');
  5505. if (dataParam === 'edit' && disableTab === 0) {
  5506. return;
  5507. }
  5508. ['click', 'touchstart'].forEach(function (eventName) {
  5509. addEvent(tab, eventName, function () {
  5510. // reset class on other elements
  5511. _self.tabs.deselectAll.call(_self);
  5512. _self.tabs.selectTab.call(_self, this, i);
  5513. });
  5514. });
  5515. });
  5516. },
  5517. /**
  5518. * Set tab as visible
  5519. * @private
  5520. * @param {HTMLDOMElement} - current tab
  5521. * @param {Number} - Index of tab in menu
  5522. */
  5523. selectTab: function (tab, index) {
  5524. var allTabs = this.popup.container
  5525. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  5526. tab.className += ' ' + PREFIX + 'tab-item-active';
  5527. allTabs[index].className += ' ' + PREFIX + 'tab-item-show';
  5528. },
  5529. /**
  5530. * Set all tabs as invisible.
  5531. * @private
  5532. */
  5533. deselectAll: function () {
  5534. var popupDiv = this.popup.container,
  5535. tabs = popupDiv
  5536. .querySelectorAll('.' + PREFIX + 'tab-item'),
  5537. tabsContent = popupDiv
  5538. .querySelectorAll('.' + PREFIX + 'tab-item-content'),
  5539. i;
  5540. for (i = 0; i < tabs.length; i++) {
  5541. tabs[i].classList.remove(PREFIX + 'tab-item-active');
  5542. tabsContent[i].classList.remove(PREFIX + 'tab-item-show');
  5543. }
  5544. }
  5545. }
  5546. };
  5547. addEvent(H.NavigationBindings, 'showPopup', function (config) {
  5548. if (!this.popup) {
  5549. // Add popup to main container
  5550. this.popup = new H.Popup(
  5551. this.chart.container, (
  5552. this.chart.options.navigation.iconsURL ||
  5553. (
  5554. this.chart.options.stockTools &&
  5555. this.chart.options.stockTools.gui.iconsURL
  5556. ) ||
  5557. 'https://code.highcharts.com/8.0.0/gfx/stock-icons/'
  5558. )
  5559. );
  5560. }
  5561. this.popup.showForm(
  5562. config.formType,
  5563. this.chart,
  5564. config.options,
  5565. config.onSubmit
  5566. );
  5567. });
  5568. addEvent(H.NavigationBindings, 'closePopup', function () {
  5569. if (this.popup) {
  5570. this.popup.closePopup();
  5571. }
  5572. });
  5573. });
  5574. _registerModule(_modules, 'masters/modules/annotations.src.js', [], function () {
  5575. });
  5576. }));