annotations-advanced.src.js 260 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230
  1. /**
  2. * @license Highcharts JS v7.0.2 (2019-01-17)
  3. * Annotations module
  4. *
  5. * (c) 2009-2019 Torstein Honsi
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. factory['default'] = factory;
  13. module.exports = factory;
  14. } else if (typeof define === 'function' && define.amd) {
  15. define(function () {
  16. return factory;
  17. });
  18. } else {
  19. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  20. }
  21. }(function (Highcharts) {
  22. var eventEmitterMixin = (function (H) {
  23. /**
  24. * It provides methods for:
  25. * - adding and handling DOM events and a drag event,
  26. * - mapping a mouse move event to the distance between two following events.
  27. * The units of the distance are specific to a transformation,
  28. * e.g. for rotation they are radians, for scaling they are scale factors.
  29. *
  30. * @mixin
  31. * @memberOf Annotation
  32. */
  33. var eventEmitterMixin = {
  34. /**
  35. * Add emitter events.
  36. */
  37. addEvents: function () {
  38. var emitter = this;
  39. H.addEvent(
  40. emitter.graphic.element,
  41. 'mousedown',
  42. function (e) {
  43. emitter.onMouseDown(e);
  44. }
  45. );
  46. H.objectEach(emitter.options.events, function (event, type) {
  47. var eventHandler = function (e) {
  48. if (type !== 'click' || !emitter.cancelClick) {
  49. event.call(
  50. emitter,
  51. emitter.chart.pointer.normalize(e),
  52. emitter.target
  53. );
  54. }
  55. };
  56. if (type !== 'drag') {
  57. emitter.graphic.on(type, eventHandler);
  58. } else {
  59. H.addEvent(emitter, 'drag', eventHandler);
  60. }
  61. });
  62. if (emitter.options.draggable) {
  63. H.addEvent(emitter, 'drag', emitter.onDrag);
  64. if (!emitter.graphic.renderer.styledMode) {
  65. emitter.graphic.css({
  66. cursor: {
  67. x: 'ew-resize',
  68. y: 'ns-resize',
  69. xy: 'move'
  70. }[emitter.options.draggable]
  71. });
  72. }
  73. }
  74. },
  75. /**
  76. * Remove emitter document events.
  77. */
  78. removeDocEvents: function () {
  79. if (this.removeDrag) {
  80. this.removeDrag = this.removeDrag();
  81. }
  82. if (this.removeMouseUp) {
  83. this.removeMouseUp = this.removeMouseUp();
  84. }
  85. },
  86. /**
  87. * Mouse down handler.
  88. *
  89. * @param {Object} e event
  90. */
  91. onMouseDown: function (e) {
  92. var emitter = this,
  93. pointer = emitter.chart.pointer,
  94. prevChartX,
  95. prevChartY;
  96. // On right click, do nothing:
  97. if (e.button === 2) {
  98. return;
  99. }
  100. e.stopPropagation();
  101. e = pointer.normalize(e);
  102. prevChartX = e.chartX;
  103. prevChartY = e.chartY;
  104. emitter.cancelClick = false;
  105. emitter.removeDrag = H.addEvent(
  106. H.doc,
  107. 'mousemove',
  108. function (e) {
  109. emitter.hasDragged = true;
  110. e = pointer.normalize(e);
  111. e.prevChartX = prevChartX;
  112. e.prevChartY = prevChartY;
  113. H.fireEvent(emitter, 'drag', e);
  114. prevChartX = e.chartX;
  115. prevChartY = e.chartY;
  116. }
  117. );
  118. emitter.removeMouseUp = H.addEvent(
  119. H.doc,
  120. 'mouseup',
  121. function (e) {
  122. emitter.cancelClick = emitter.hasDragged;
  123. emitter.hasDragged = false;
  124. emitter.onMouseUp(e);
  125. }
  126. );
  127. },
  128. /**
  129. * Mouse up handler.
  130. *
  131. * @param {Object} e event
  132. */
  133. onMouseUp: function () {
  134. var chart = this.chart,
  135. annotation = this.target || this,
  136. annotationsOptions = chart.options.annotations,
  137. index = chart.annotations.indexOf(annotation);
  138. this.removeDocEvents();
  139. annotationsOptions[index] = annotation.options;
  140. },
  141. /**
  142. * Drag and drop event. All basic annotations should share this
  143. * capability as well as the extended ones.
  144. *
  145. * @param {Object} e event
  146. */
  147. onDrag: function (e) {
  148. if (
  149. this.chart.isInsidePlot(
  150. e.chartX - this.chart.plotLeft,
  151. e.chartY - this.chart.plotTop
  152. )
  153. ) {
  154. var translation = this.mouseMoveToTranslation(e);
  155. if (this.options.draggable === 'x') {
  156. translation.y = 0;
  157. }
  158. if (this.options.draggable === 'y') {
  159. translation.x = 0;
  160. }
  161. if (this.points.length) {
  162. this.translate(translation.x, translation.y);
  163. } else {
  164. this.shapes.forEach(function (shape) {
  165. shape.translate(translation.x, translation.y);
  166. });
  167. this.labels.forEach(function (label) {
  168. label.translate(translation.x, translation.y);
  169. });
  170. }
  171. this.redraw(false);
  172. }
  173. },
  174. /**
  175. * Map mouse move event to the radians.
  176. *
  177. * @param {Object} e event
  178. * @param {number} cx center x
  179. * @param {number} cy center y
  180. */
  181. mouseMoveToRadians: function (e, cx, cy) {
  182. var prevDy = e.prevChartY - cy,
  183. prevDx = e.prevChartX - cx,
  184. dy = e.chartY - cy,
  185. dx = e.chartX - cx,
  186. temp;
  187. if (this.chart.inverted) {
  188. temp = prevDx;
  189. prevDx = prevDy;
  190. prevDy = temp;
  191. temp = dx;
  192. dx = dy;
  193. dy = temp;
  194. }
  195. return Math.atan2(dy, dx) - Math.atan2(prevDy, prevDx);
  196. },
  197. /**
  198. * Map mouse move event to the distance between two following events.
  199. *
  200. * @param {Object} e event
  201. */
  202. mouseMoveToTranslation: function (e) {
  203. var dx = e.chartX - e.prevChartX,
  204. dy = e.chartY - e.prevChartY,
  205. temp;
  206. if (this.chart.inverted) {
  207. temp = dy;
  208. dy = dx;
  209. dx = temp;
  210. }
  211. return {
  212. x: dx,
  213. y: dy
  214. };
  215. },
  216. /**
  217. * Map mouse move to the scale factors.
  218. *
  219. * @param {Object} e event
  220. * @param {number} cx center x
  221. * @param {number} cy center y
  222. **/
  223. mouseMoveToScale: function (e, cx, cy) {
  224. var prevDx = e.prevChartX - cx,
  225. prevDy = e.prevChartY - cy,
  226. dx = e.chartX - cx,
  227. dy = e.chartY - cy,
  228. sx = (dx || 1) / (prevDx || 1),
  229. sy = (dy || 1) / (prevDy || 1),
  230. temp;
  231. if (this.chart.inverted) {
  232. temp = sy;
  233. sy = sx;
  234. sx = temp;
  235. }
  236. return {
  237. x: sx,
  238. y: sy
  239. };
  240. },
  241. /**
  242. * Destroy the event emitter.
  243. */
  244. destroy: function () {
  245. this.removeDocEvents();
  246. H.removeEvent(this);
  247. this.hcEvents = null;
  248. }
  249. };
  250. return eventEmitterMixin;
  251. }(Highcharts));
  252. var ControlPoint = (function (H, eventEmitterMixin) {
  253. /**
  254. * A control point class which is a connection between controllable
  255. * transform methods and a user actions.
  256. *
  257. * @constructor
  258. * @mixes eventEmitterMixin
  259. * @memberOf Annotation
  260. *
  261. * @param {Highcharts.Chart} chart a chart instance
  262. * @param {Object} target a controllable instance which is a target for
  263. * a control point
  264. * @param {Annotation.ControlPoint.Options} options an options object
  265. * @param {number} [index]
  266. **/
  267. function ControlPoint(chart, target, options, index) {
  268. this.chart = chart;
  269. this.target = target;
  270. this.options = options;
  271. this.index = H.pick(options.index, index);
  272. }
  273. /**
  274. * @typedef {Object} Annotation.ControlPoint.Position
  275. * @property {number} x
  276. * @property {number} y
  277. */
  278. /**
  279. * @callback Annotation.ControlPoint.Positioner
  280. * @param {Object} e event
  281. * @param {Controllable} target
  282. * @return {Annotation.ControlPoint.Position} position
  283. */
  284. /**
  285. * @typedef {Object} Annotation.ControlPoint.Options
  286. * @property {string} symbol
  287. * @property {number} width
  288. * @property {number} height
  289. * @property {Object} style
  290. * @property {boolean} visible
  291. * @property {Annotation.ControlPoint.Positioner} positioner
  292. * @property {Object} events
  293. */
  294. H.extend(
  295. ControlPoint.prototype,
  296. eventEmitterMixin
  297. );
  298. /**
  299. * Set the visibility.
  300. *
  301. * @param {boolean} [visible]
  302. **/
  303. ControlPoint.prototype.setVisibility = function (visible) {
  304. this.graphic.attr('visibility', visible ? 'visible' : 'hidden');
  305. this.options.visible = visible;
  306. };
  307. /**
  308. * Render the control point.
  309. */
  310. ControlPoint.prototype.render = function () {
  311. var chart = this.chart,
  312. options = this.options;
  313. this.graphic = chart.renderer
  314. .symbol(
  315. options.symbol,
  316. 0,
  317. 0,
  318. options.width,
  319. options.height
  320. )
  321. .add(chart.controlPointsGroup)
  322. .css(options.style);
  323. this.setVisibility(options.visible);
  324. this.addEvents();
  325. };
  326. /**
  327. * Redraw the control point.
  328. *
  329. * @param {boolean} [animation]
  330. */
  331. ControlPoint.prototype.redraw = function (animation) {
  332. this.graphic[animation ? 'animate' : 'attr'](
  333. this.options.positioner.call(this, this.target)
  334. );
  335. };
  336. /**
  337. * Destroy the control point.
  338. */
  339. ControlPoint.prototype.destroy = function () {
  340. eventEmitterMixin.destroy.call(this);
  341. if (this.graphic) {
  342. this.graphic = this.graphic.destroy();
  343. }
  344. this.chart = null;
  345. this.target = null;
  346. this.options = null;
  347. };
  348. /**
  349. * Update the control point.
  350. */
  351. ControlPoint.prototype.update = function (userOptions) {
  352. var chart = this.chart,
  353. target = this.target,
  354. index = this.index,
  355. options = H.merge(true, this.options, userOptions);
  356. this.destroy();
  357. this.constructor(chart, target, options, index);
  358. this.render(chart.controlPointsGroup);
  359. this.redraw();
  360. };
  361. return ControlPoint;
  362. }(Highcharts, eventEmitterMixin));
  363. var MockPoint = (function (H) {
  364. /**
  365. * A point-like object, a mock point or a point uses in series.
  366. *
  367. * @typedef {Highcharts.Point | Annotation.MockPoint} Annotation.PointLike
  368. */
  369. /**
  370. * A mock point configuration.
  371. *
  372. * @typedef {Object} Annotation.MockPoint.Options
  373. * @property {number} x x value for the point in xAxis scale or pixels
  374. * @property {number} y y value for the point in yAxis scale or pixels
  375. * @property {string|number|Highcharts.Axis} [xAxis] xAxis instance, index or id
  376. * @property {string|number|Highcharts.Axis} [yAxis] yAxis instance, index or id
  377. */
  378. /**
  379. * A trimmed point object which imitates {@link Highchart.Point} class.
  380. * It is created when there is a need of pointing to some chart's position
  381. * using axis values or pixel values
  382. *
  383. * @class
  384. * @memberOf Annotation
  385. *
  386. * @param {Chart} chart a chart instance
  387. * @param {Controllable} [target] a controllable instance
  388. * @param {Annotation.MockPoint.Options} options an options object
  389. */
  390. function MockPoint(chart, target, options) {
  391. /**
  392. * A mock series instance imitating a real series from a real point.
  393. *
  394. * @type {Object}
  395. * @property {boolean} series.visible=true - whether a series is visible
  396. * @property {Chart} series.chart - a chart instance
  397. * @property {function} series.getPlotBox
  398. */
  399. this.series = {
  400. visible: true,
  401. chart: chart,
  402. getPlotBox: H.Series.prototype.getPlotBox
  403. };
  404. /**
  405. * @type {?Controllable}
  406. */
  407. this.target = target || null;
  408. /**
  409. * Options for the mock point.
  410. *
  411. * @type {Annotation.MockPoint.Options}
  412. */
  413. this.options = options;
  414. /**
  415. * If an xAxis is set it represents the point's value in terms of the xAxis.
  416. *
  417. * @name Annotation.MockPoint#x
  418. * @type {?number}
  419. */
  420. /**
  421. * If an yAxis is set it represents the point's value in terms of the yAxis.
  422. *
  423. * @name Annotation.MockPoint#y
  424. * @type {?number}
  425. */
  426. /**
  427. * It represents the point's pixel x coordinate relative to its plot box.
  428. *
  429. * @name Annotation.MockPoint#plotX
  430. * @type {?number}
  431. */
  432. /**
  433. * It represents the point's pixel y position relative to its plot box.
  434. *
  435. * @name Annotation.MockPoint#plotY
  436. * @type {?number}
  437. */
  438. /**
  439. * Whether the point is inside the plot box.
  440. *
  441. * @name Annotation.MockPoint#isInside
  442. * @type {boolean}
  443. */
  444. this.applyOptions(this.getOptions());
  445. }
  446. /**
  447. * Create a mock point from a real Highcharts point.
  448. *
  449. * @param {Point} point
  450. *
  451. * @return {Annotation.MockPoint} a mock point instance.
  452. */
  453. MockPoint.fromPoint = function (point) {
  454. return new MockPoint(point.series.chart, null, {
  455. x: point.x,
  456. y: point.y,
  457. xAxis: point.series.xAxis,
  458. yAxis: point.series.yAxis
  459. });
  460. };
  461. /**
  462. * @typedef Annotation.MockPoint.Position
  463. * @property {number} x
  464. * @property {number} y
  465. */
  466. /**
  467. * Get the pixel position from the point like object.
  468. *
  469. * @param {Annotation.PointLike} point
  470. * @param {boolean} [paneCoordinates]
  471. * whether the pixel position should be relative
  472. *
  473. * @return {Annotation.MockPoint.Position} pixel position
  474. */
  475. MockPoint.pointToPixels = function (point, paneCoordinates) {
  476. var series = point.series,
  477. chart = series.chart,
  478. x = point.plotX,
  479. y = point.plotY,
  480. plotBox;
  481. if (chart.inverted) {
  482. if (point.mock) {
  483. x = point.plotY;
  484. y = point.plotX;
  485. } else {
  486. x = chart.plotWidth - point.plotY;
  487. y = chart.plotHeight - point.plotX;
  488. }
  489. }
  490. if (series && !paneCoordinates) {
  491. plotBox = series.getPlotBox();
  492. x += plotBox.translateX;
  493. y += plotBox.translateY;
  494. }
  495. return {
  496. x: x,
  497. y: y
  498. };
  499. };
  500. /**
  501. * Get fresh mock point options from the point like object.
  502. *
  503. * @param {Annotation.PointLike} point
  504. *
  505. * @return {Annotation.MockPoint.Options} mock point's options
  506. */
  507. MockPoint.pointToOptions = function (point) {
  508. return {
  509. x: point.x,
  510. y: point.y,
  511. xAxis: point.series.xAxis,
  512. yAxis: point.series.yAxis
  513. };
  514. };
  515. H.extend(MockPoint.prototype, /** @lends Annotation.MockPoint# */ {
  516. /**
  517. * A flag indicating that a point is not the real one.
  518. *
  519. * @type {boolean}
  520. * @default true
  521. */
  522. mock: true,
  523. /**
  524. * Check if the point has dynamic options.
  525. *
  526. * @return {boolean} A positive flag if the point has dynamic options.
  527. */
  528. hasDynamicOptions: function () {
  529. return typeof this.options === 'function';
  530. },
  531. /**
  532. * Get the point's options.
  533. *
  534. * @return {Annotation.MockPoint.Options} the mock point's options.
  535. */
  536. getOptions: function () {
  537. return this.hasDynamicOptions() ?
  538. this.options(this.target) :
  539. this.options;
  540. },
  541. /**
  542. * Apply options for the point.
  543. *
  544. * @param {Annotation.MockPoint.Options} options
  545. */
  546. applyOptions: function (options) {
  547. this.command = options.command;
  548. this.setAxis(options, 'x');
  549. this.setAxis(options, 'y');
  550. this.refresh();
  551. },
  552. /**
  553. * Set x or y axis.
  554. *
  555. * @param {Annotation.MockPoint.Options} options
  556. * @param {string} xOrY 'x' or 'y' string literal
  557. */
  558. setAxis: function (options, xOrY) {
  559. var axisName = xOrY + 'Axis',
  560. axisOptions = options[axisName],
  561. chart = this.series.chart;
  562. this.series[axisName] =
  563. axisOptions instanceof H.Axis ?
  564. axisOptions :
  565. H.defined(axisOptions) ?
  566. chart[axisName][axisOptions] || chart.get(axisOptions) :
  567. null;
  568. },
  569. /**
  570. * Transform the mock point to an anchor
  571. * (relative position on the chart).
  572. *
  573. * @return {Array<number>} A quadruple of numbers which denotes x, y,
  574. * width and height of the box
  575. **/
  576. toAnchor: function () {
  577. var anchor = [this.plotX, this.plotY, 0, 0];
  578. if (this.series.chart.inverted) {
  579. anchor[0] = this.plotY;
  580. anchor[1] = this.plotX;
  581. }
  582. return anchor;
  583. },
  584. /**
  585. * @typedef {Object} Annotation.MockPoint.LabelConfig
  586. * @property {number|undefined} x x value translated to x axis scale
  587. * @property {number|undefined} y y value translated to y axis scale
  588. * @property {Annotation.MockPoint} point instance of the point
  589. */
  590. /**
  591. * Returns a label config object -
  592. * the same as Highcharts.Point.prototype.getLabelConfig
  593. *
  594. * @return {Annotation.MockPoint.LabelConfig} the point's label config
  595. */
  596. getLabelConfig: function () {
  597. return {
  598. x: this.x,
  599. y: this.y,
  600. point: this
  601. };
  602. },
  603. /**
  604. * Check if the point is inside its pane.
  605. *
  606. * @return {boolean} A flag indicating whether the point is inside the pane.
  607. */
  608. isInsidePane: function () {
  609. var plotX = this.plotX,
  610. plotY = this.plotY,
  611. xAxis = this.series.xAxis,
  612. yAxis = this.series.yAxis,
  613. isInside = true;
  614. if (xAxis) {
  615. isInside = H.defined(plotX) && plotX >= 0 && plotX <= xAxis.len;
  616. }
  617. if (yAxis) {
  618. isInside =
  619. isInside &&
  620. H.defined(plotY) &&
  621. plotY >= 0 && plotY <= yAxis.len;
  622. }
  623. return isInside;
  624. },
  625. /**
  626. * Refresh point values and coordinates based on its options.
  627. */
  628. refresh: function () {
  629. var series = this.series,
  630. xAxis = series.xAxis,
  631. yAxis = series.yAxis,
  632. options = this.getOptions();
  633. if (xAxis) {
  634. this.x = options.x;
  635. this.plotX = xAxis.toPixels(options.x, true);
  636. } else {
  637. this.x = null;
  638. this.plotX = options.x;
  639. }
  640. if (yAxis) {
  641. this.y = options.y;
  642. this.plotY = yAxis.toPixels(options.y, true);
  643. } else {
  644. this.y = null;
  645. this.plotY = options.y;
  646. }
  647. this.isInside = this.isInsidePane();
  648. },
  649. /**
  650. * Translate the point.
  651. *
  652. * @param {number} [cx] origin x transformation
  653. * @param {number} [cy] origin y transformation
  654. * @param {number} dx translation for x coordinate
  655. * @param {number} dy translation for y coordinate
  656. **/
  657. translate: function (cx, cy, dx, dy) {
  658. if (!this.hasDynamicOptions()) {
  659. this.plotX += dx;
  660. this.plotY += dy;
  661. this.refreshOptions();
  662. }
  663. },
  664. /**
  665. * Scale the point.
  666. *
  667. * @param {number} cx origin x transformation
  668. * @param {number} cy origin y transformation
  669. * @param {number} sx scale factor x
  670. * @param {number} sy scale factor y
  671. */
  672. scale: function (cx, cy, sx, sy) {
  673. if (!this.hasDynamicOptions()) {
  674. var x = this.plotX * sx,
  675. y = this.plotY * sy,
  676. tx = (1 - sx) * cx,
  677. ty = (1 - sy) * cy;
  678. this.plotX = tx + x;
  679. this.plotY = ty + y;
  680. this.refreshOptions();
  681. }
  682. },
  683. /**
  684. * Rotate the point.
  685. *
  686. * @param {number} cx origin x rotation
  687. * @param {number} cy origin y rotation
  688. * @param {number} radians
  689. */
  690. rotate: function (cx, cy, radians) {
  691. if (!this.hasDynamicOptions()) {
  692. var cos = Math.cos(radians),
  693. sin = Math.sin(radians),
  694. x = this.plotX,
  695. y = this.plotY,
  696. tx,
  697. ty;
  698. x -= cx;
  699. y -= cy;
  700. tx = x * cos - y * sin;
  701. ty = x * sin + y * cos;
  702. this.plotX = tx + cx;
  703. this.plotY = ty + cy;
  704. this.refreshOptions();
  705. }
  706. },
  707. /**
  708. * Refresh point options based on its plot coordinates.
  709. */
  710. refreshOptions: function () {
  711. var series = this.series,
  712. xAxis = series.xAxis,
  713. yAxis = series.yAxis;
  714. this.x = this.options.x = xAxis ?
  715. this.options.x = xAxis.toValue(this.plotX, true) :
  716. this.plotX;
  717. this.y = this.options.y = yAxis ?
  718. yAxis.toValue(this.plotY, true) :
  719. this.plotY;
  720. }
  721. });
  722. return MockPoint;
  723. }(Highcharts));
  724. var controllableMixin = (function (H, ControlPoint, MockPoint) {
  725. /**
  726. * It provides methods for handling points, control points
  727. * and points transformations.
  728. *
  729. * @mixin
  730. * @memberOf Annotation
  731. */
  732. var controllableMixin = {
  733. /**
  734. * Init the controllable
  735. *
  736. * @param {Annotation} annotation - an annotation instance
  737. * @param {Object} options - options specific for controllable
  738. * @param {number} index - index of the controllable element
  739. **/
  740. init: function (annotation, options, index) {
  741. this.annotation = annotation;
  742. this.chart = annotation.chart;
  743. this.options = options;
  744. this.points = [];
  745. this.controlPoints = [];
  746. this.index = index;
  747. this.linkPoints();
  748. this.addControlPoints();
  749. },
  750. /**
  751. * Redirect attr usage on the controllable graphic element.
  752. **/
  753. attr: function () {
  754. this.graphic.attr.apply(this.graphic, arguments);
  755. },
  756. /**
  757. * Get the controllable's points options.
  758. *
  759. * @return {Array<PointLikeOptions>} - an array of points' options.
  760. *
  761. */
  762. getPointsOptions: function () {
  763. var options = this.options;
  764. return options.points || (options.point && H.splat(options.point));
  765. },
  766. /**
  767. * Utility function for mapping item's options
  768. * to element's attribute
  769. *
  770. * @param {Object} options
  771. * @return {Object} mapped options
  772. **/
  773. attrsFromOptions: function (options) {
  774. var map = this.constructor.attrsMap,
  775. attrs = {},
  776. key,
  777. mappedKey,
  778. styledMode = this.chart.styledMode;
  779. for (key in options) {
  780. mappedKey = map[key];
  781. if (
  782. mappedKey &&
  783. (
  784. !styledMode ||
  785. ['fill', 'stroke', 'stroke-width']
  786. .indexOf(mappedKey) === -1
  787. )
  788. ) {
  789. attrs[mappedKey] = options[key];
  790. }
  791. }
  792. return attrs;
  793. },
  794. /**
  795. * @typedef {Object} Annotation.controllableMixin.Position
  796. * @property {number} x
  797. * @property {number} y
  798. */
  799. /**
  800. * An object which denotes an anchor position
  801. *
  802. * @typedef Annotation.controllableMixin.AnchorPosition
  803. * Annotation.controllableMixin.Position
  804. * @property {number} height
  805. * @property {number} width
  806. */
  807. /**
  808. * An object which denots a controllable's anchor positions
  809. * - relative and absolute.
  810. *
  811. * @typedef {Object} Annotation.controllableMixin.Anchor
  812. * @property {Annotation.controllableMixin.AnchorPosition} relativePosition
  813. * @property {Annotation.controllableMixin.AnchorPosition} absolutePosition
  814. */
  815. /**
  816. * Returns object which denotes anchor position - relative and absolute.
  817. *
  818. * @param {Annotation.PointLike} point a point like object
  819. * @return {Annotation.controllableMixin.Anchor} a controllable anchor
  820. */
  821. anchor: function (point) {
  822. var plotBox = point.series.getPlotBox(),
  823. box = point.mock ?
  824. point.toAnchor() :
  825. H.Tooltip.prototype.getAnchor.call({
  826. chart: point.series.chart
  827. }, point),
  828. anchor = {
  829. x: box[0] + (this.options.x || 0),
  830. y: box[1] + (this.options.y || 0),
  831. height: box[2] || 0,
  832. width: box[3] || 0
  833. };
  834. return {
  835. relativePosition: anchor,
  836. absolutePosition: H.merge(anchor, {
  837. x: anchor.x + plotBox.translateX,
  838. y: anchor.y + plotBox.translateY
  839. })
  840. };
  841. },
  842. /**
  843. * Map point's options to a point-like object.
  844. *
  845. * @param {Annotation.MockPoint.Options} pointOptions point's options
  846. * @param {Annotation.PointLike} point a point like instance
  847. * @return {Annotation.PointLike|null} if the point is
  848. * found/set returns this point, otherwise null
  849. */
  850. point: function (pointOptions, point) {
  851. if (pointOptions && pointOptions.series) {
  852. return pointOptions;
  853. }
  854. if (!point || point.series === null) {
  855. if (H.isObject(pointOptions)) {
  856. point = new MockPoint(
  857. this.chart,
  858. this,
  859. pointOptions
  860. );
  861. } else if (H.isString(pointOptions)) {
  862. point = this.chart.get(pointOptions) || null;
  863. } else if (typeof pointOptions === 'function') {
  864. var pointConfig = pointOptions.call(point, this);
  865. point = pointConfig.series ?
  866. pointConfig :
  867. new MockPoint(
  868. this.chart,
  869. this,
  870. pointOptions
  871. );
  872. }
  873. }
  874. return point;
  875. },
  876. /**
  877. * Find point-like objects based on points options.
  878. *
  879. * @return {Array<Annotation.PointLike>} an array of point-like objects
  880. */
  881. linkPoints: function () {
  882. var pointsOptions = this.getPointsOptions(),
  883. points = this.points,
  884. len = (pointsOptions && pointsOptions.length) || 0,
  885. i,
  886. point;
  887. for (i = 0; i < len; i++) {
  888. point = this.point(pointsOptions[i], points[i]);
  889. if (!point) {
  890. points.length = 0;
  891. return;
  892. }
  893. if (point.mock) {
  894. point.refresh();
  895. }
  896. points[i] = point;
  897. }
  898. return points;
  899. },
  900. /**
  901. * Add control points to a controllable.
  902. */
  903. addControlPoints: function () {
  904. var controlPointsOptions = this.options.controlPoints;
  905. (controlPointsOptions || []).forEach(
  906. function (controlPointOptions, i) {
  907. var options = H.merge(
  908. this.options.controlPointOptions,
  909. controlPointOptions
  910. );
  911. if (!options.index) {
  912. options.index = i;
  913. }
  914. controlPointsOptions[i] = options;
  915. this.controlPoints.push(
  916. new ControlPoint(this.chart, this, options)
  917. );
  918. },
  919. this
  920. );
  921. },
  922. /**
  923. * Check if a controllable should be rendered/redrawn.
  924. *
  925. * @return {boolean} whether a controllable should be drawn.
  926. */
  927. shouldBeDrawn: function () {
  928. return Boolean(this.points.length);
  929. },
  930. /**
  931. * Render a controllable.
  932. **/
  933. render: function () {
  934. this.controlPoints.forEach(function (controlPoint) {
  935. controlPoint.render();
  936. });
  937. },
  938. /**
  939. * Redraw a controllable.
  940. *
  941. * @param {boolean} animation
  942. **/
  943. redraw: function (animation) {
  944. this.controlPoints.forEach(function (controlPoint) {
  945. controlPoint.redraw(animation);
  946. });
  947. },
  948. /**
  949. * Transform a controllable with a specific transformation.
  950. *
  951. * @param {string} transformation a transformation name
  952. * @param {number} cx origin x transformation
  953. * @param {number} cy origin y transformation
  954. * @param {number} p1 param for the transformation
  955. * @param {number} p2 param for the transformation
  956. **/
  957. transform: function (transformation, cx, cy, p1, p2) {
  958. if (this.chart.inverted) {
  959. var temp = cx;
  960. cx = cy;
  961. cy = temp;
  962. }
  963. this.points.forEach(function (point, i) {
  964. this.transformPoint(transformation, cx, cy, p1, p2, i);
  965. }, this);
  966. },
  967. /**
  968. * Transform a point with a specific transformation
  969. * If a transformed point is a real point it is replaced with
  970. * the mock point.
  971. *
  972. * @param {string} transformation a transformation name
  973. * @param {number} cx origin x transformation
  974. * @param {number} cy origin y transformation
  975. * @param {number} p1 param for the transformation
  976. * @param {number} p2 param for the transformation
  977. * @param {number} i index of the point
  978. *
  979. **/
  980. transformPoint: function (transformation, cx, cy, p1, p2, i) {
  981. var point = this.points[i];
  982. if (!point.mock) {
  983. point = this.points[i] = MockPoint.fromPoint(point);
  984. }
  985. point[transformation](cx, cy, p1, p2);
  986. },
  987. /**
  988. * Translate a controllable.
  989. *
  990. * @param {number} dx translation for x coordinate
  991. * @param {number} dy translation for y coordinate
  992. **/
  993. translate: function (dx, dy) {
  994. this.transform('translate', null, null, dx, dy);
  995. },
  996. /**
  997. * Translate a specific point within a controllable.
  998. *
  999. * @param {number} dx translation for x coordinate
  1000. * @param {number} dy translation for y coordinate
  1001. * @param {number} i index of the point
  1002. **/
  1003. translatePoint: function (dx, dy, i) {
  1004. this.transformPoint('translate', null, null, dx, dy, i);
  1005. },
  1006. /**
  1007. * Rotate a controllable.
  1008. *
  1009. * @param {number} cx origin x rotation
  1010. * @param {number} cy origin y rotation
  1011. * @param {number} radians
  1012. **/
  1013. rotate: function (cx, cy, radians) {
  1014. this.transform('rotate', cx, cy, radians);
  1015. },
  1016. /**
  1017. * Scale a controllable.
  1018. *
  1019. * @param {number} cx origin x rotation
  1020. * @param {number} cy origin y rotation
  1021. * @param {number} sx scale factor x
  1022. * @param {number} sy scale factor y
  1023. */
  1024. scale: function (cx, cy, sx, sy) {
  1025. this.transform('scale', cx, cy, sx, sy);
  1026. },
  1027. /**
  1028. * Set control points' visibility.
  1029. *
  1030. * @param {boolean} [visible]
  1031. */
  1032. setControlPointsVisibility: function (visible) {
  1033. this.controlPoints.forEach(function (controlPoint) {
  1034. controlPoint.setVisibility(visible);
  1035. });
  1036. },
  1037. /**
  1038. * Destroy a controllable.
  1039. */
  1040. destroy: function () {
  1041. if (this.graphic) {
  1042. this.graphic = this.graphic.destroy();
  1043. }
  1044. if (this.tracker) {
  1045. this.tracker = this.tracker.destroy();
  1046. }
  1047. this.controlPoints.forEach(function (controlPoint) {
  1048. controlPoint.destroy();
  1049. });
  1050. this.chart = null;
  1051. this.points = null;
  1052. this.controlPoints = null;
  1053. this.options = null;
  1054. if (this.annotation) {
  1055. this.annotation = null;
  1056. }
  1057. },
  1058. /**
  1059. * Update a controllable.
  1060. *
  1061. * @param {Object} newOptions
  1062. */
  1063. update: function (newOptions) {
  1064. var annotation = this.annotation,
  1065. options = H.merge(true, this.options, newOptions),
  1066. parentGroup = this.graphic.parentGroup;
  1067. this.destroy();
  1068. this.constructor(annotation, options);
  1069. this.render(parentGroup);
  1070. this.redraw();
  1071. }
  1072. };
  1073. return controllableMixin;
  1074. }(Highcharts, ControlPoint, MockPoint));
  1075. var markerMixin = (function (H) {
  1076. /**
  1077. * Options for configuring markers for annotations.
  1078. *
  1079. * An example of the arrow marker:
  1080. * <pre>
  1081. * {
  1082. * arrow: {
  1083. * id: 'arrow',
  1084. * tagName: 'marker',
  1085. * refY: 5,
  1086. * refX: 5,
  1087. * markerWidth: 10,
  1088. * markerHeight: 10,
  1089. * children: [{
  1090. * tagName: 'path',
  1091. * attrs: {
  1092. * d: 'M 0 0 L 10 5 L 0 10 Z',
  1093. * strokeWidth: 0
  1094. * }
  1095. * }]
  1096. * }
  1097. * }
  1098. * </pre>
  1099. * @type {Object}
  1100. * @sample highcharts/annotations/custom-markers/
  1101. * Define a custom marker for annotations
  1102. * @sample highcharts/css/annotations-markers/
  1103. * Define markers in a styled mode
  1104. * @since 6.0.0
  1105. * @apioption defs
  1106. */
  1107. var defaultMarkers = {
  1108. arrow: {
  1109. tagName: 'marker',
  1110. render: false,
  1111. id: 'arrow',
  1112. refY: 5,
  1113. refX: 9,
  1114. markerWidth: 10,
  1115. markerHeight: 10,
  1116. children: [{
  1117. tagName: 'path',
  1118. d: 'M 0 0 L 10 5 L 0 10 Z', // triangle (used as an arrow)
  1119. strokeWidth: 0
  1120. }]
  1121. },
  1122. 'reverse-arrow': {
  1123. tagName: 'marker',
  1124. render: false,
  1125. id: 'reverse-arrow',
  1126. refY: 5,
  1127. refX: 1,
  1128. markerWidth: 10,
  1129. markerHeight: 10,
  1130. children: [{
  1131. tagName: 'path',
  1132. // reverse triangle (used as an arrow)
  1133. d: 'M 0 5 L 10 0 L 10 10 Z',
  1134. strokeWidth: 0
  1135. }]
  1136. }
  1137. };
  1138. H.SVGRenderer.prototype.addMarker = function (id, markerOptions) {
  1139. var options = { id: id };
  1140. var attrs = {
  1141. stroke: markerOptions.color || 'none',
  1142. fill: markerOptions.color || 'rgba(0, 0, 0, 0.75)'
  1143. };
  1144. options.children = markerOptions.children.map(function (child) {
  1145. return H.merge(attrs, child);
  1146. });
  1147. var marker = this.definition(H.merge(true, {
  1148. markerWidth: 20,
  1149. markerHeight: 20,
  1150. refX: 0,
  1151. refY: 0,
  1152. orient: 'auto'
  1153. }, markerOptions, options));
  1154. marker.id = id;
  1155. return marker;
  1156. };
  1157. var createMarkerSetter = function (markerType) {
  1158. return function (value) {
  1159. this.attr(markerType, 'url(#' + value + ')');
  1160. };
  1161. };
  1162. /**
  1163. * @mixin
  1164. */
  1165. var markerMixin = {
  1166. markerEndSetter: createMarkerSetter('marker-end'),
  1167. markerStartSetter: createMarkerSetter('marker-start'),
  1168. /*
  1169. * Set markers.
  1170. *
  1171. * @param {Controllable} item
  1172. */
  1173. setItemMarkers: function (item) {
  1174. var itemOptions = item.options,
  1175. chart = item.chart,
  1176. defs = chart.options.defs,
  1177. fill = itemOptions.fill,
  1178. color = H.defined(fill) && fill !== 'none' ?
  1179. fill :
  1180. itemOptions.stroke,
  1181. setMarker = function (markerType) {
  1182. var markerId = itemOptions[markerType],
  1183. def,
  1184. predefinedMarker,
  1185. key,
  1186. marker;
  1187. if (markerId) {
  1188. for (key in defs) {
  1189. def = defs[key];
  1190. if (
  1191. markerId === def.id && def.tagName === 'marker'
  1192. ) {
  1193. predefinedMarker = def;
  1194. break;
  1195. }
  1196. }
  1197. if (predefinedMarker) {
  1198. marker = item[markerType] = chart.renderer
  1199. .addMarker(
  1200. (itemOptions.id || H.uniqueKey()) + '-' +
  1201. predefinedMarker.id,
  1202. H.merge(predefinedMarker, { color: color })
  1203. );
  1204. item.attr(markerType, marker.attr('id'));
  1205. }
  1206. }
  1207. };
  1208. ['markerStart', 'markerEnd'].forEach(setMarker);
  1209. }
  1210. };
  1211. // In a styled mode definition is implemented
  1212. H.SVGRenderer.prototype.definition = function (def) {
  1213. var ren = this;
  1214. function recurse(config, parent) {
  1215. var ret;
  1216. H.splat(config).forEach(function (item) {
  1217. var node = ren.createElement(item.tagName),
  1218. attr = {};
  1219. // Set attributes
  1220. H.objectEach(item, function (val, key) {
  1221. if (
  1222. key !== 'tagName' &&
  1223. key !== 'children' &&
  1224. key !== 'textContent'
  1225. ) {
  1226. attr[key] = val;
  1227. }
  1228. });
  1229. node.attr(attr);
  1230. // Add to the tree
  1231. node.add(parent || ren.defs);
  1232. // Add text content
  1233. if (item.textContent) {
  1234. node.element.appendChild(
  1235. H.doc.createTextNode(item.textContent)
  1236. );
  1237. }
  1238. // Recurse
  1239. recurse(item.children || [], node);
  1240. ret = node;
  1241. });
  1242. // Return last node added (on top level it's the only one)
  1243. return ret;
  1244. }
  1245. return recurse(def);
  1246. };
  1247. H.addEvent(H.Chart, 'afterGetContainer', function () {
  1248. this.options.defs = H.merge(defaultMarkers, this.options.defs || {});
  1249. H.objectEach(this.options.defs, function (def) {
  1250. if (def.tagName === 'marker' && def.render !== false) {
  1251. this.renderer.addMarker(def.id, def);
  1252. }
  1253. }, this);
  1254. });
  1255. return markerMixin;
  1256. }(Highcharts));
  1257. var ControllablePath = (function (H, controllableMixin, markerMixin) {
  1258. // See TRACKER_FILL in highcharts.src.js
  1259. var TRACKER_FILL = 'rgba(192,192,192,' + (H.svg ? 0.0001 : 0.002) + ')';
  1260. /**
  1261. * A controllable path class.
  1262. *
  1263. * @class
  1264. * @mixes Annotation.controllableMixin
  1265. * @mixes Annotation.markerMixin
  1266. * @memberOf Annotation
  1267. *
  1268. * @param {Highcharts.Annotation}
  1269. * @param {Object} options a path's options object
  1270. * @param {number} index of the path
  1271. **/
  1272. function ControllablePath(annotation, options, index) {
  1273. this.init(annotation, options, index);
  1274. this.collection = 'shapes';
  1275. }
  1276. /**
  1277. * @typedef {Object} Annotation.ControllablePath.AttrsMap
  1278. * @property {string} dashStyle=dashstyle
  1279. * @property {string} strokeWidth=stroke-width
  1280. * @property {string} stroke=stroke
  1281. * @property {string} fill=fill
  1282. * @property {string} zIndex=zIndex
  1283. */
  1284. /**
  1285. * A map object which allows to map options attributes to element attributes
  1286. *
  1287. * @type {Annotation.ControllablePath.AttrsMap}
  1288. */
  1289. ControllablePath.attrsMap = {
  1290. dashStyle: 'dashstyle',
  1291. strokeWidth: 'stroke-width',
  1292. stroke: 'stroke',
  1293. fill: 'fill',
  1294. zIndex: 'zIndex'
  1295. };
  1296. H.merge(
  1297. true,
  1298. ControllablePath.prototype,
  1299. controllableMixin, /** @lends Annotation.ControllablePath# */ {
  1300. /**
  1301. * @type 'path'
  1302. */
  1303. type: 'path',
  1304. setMarkers: markerMixin.setItemMarkers,
  1305. /**
  1306. * Map the controllable path to 'd' path attribute
  1307. *
  1308. * @return {Array<(string|number)>} a path's d attribute
  1309. */
  1310. toD: function () {
  1311. var d = this.options.d;
  1312. if (d) {
  1313. return typeof d === 'function' ?
  1314. d.call(this) :
  1315. d;
  1316. }
  1317. var points = this.points,
  1318. len = points.length,
  1319. showPath = len,
  1320. point = points[0],
  1321. position = showPath && this.anchor(point).absolutePosition,
  1322. pointIndex = 0,
  1323. dIndex = 2,
  1324. command;
  1325. d = position && ['M', position.x, position.y];
  1326. while (++pointIndex < len && showPath) {
  1327. point = points[pointIndex];
  1328. command = point.command || 'L';
  1329. position = this.anchor(point).absolutePosition;
  1330. if (command === 'Z') {
  1331. d[++dIndex] = command;
  1332. } else {
  1333. if (command !== points[pointIndex - 1].command) {
  1334. d[++dIndex] = command;
  1335. }
  1336. d[++dIndex] = position.x;
  1337. d[++dIndex] = position.y;
  1338. }
  1339. showPath = point.series.visible;
  1340. }
  1341. return showPath ?
  1342. this.chart.renderer.crispLine(d, this.graphic.strokeWidth()) :
  1343. null;
  1344. },
  1345. shouldBeDrawn: function () {
  1346. return controllableMixin.shouldBeDrawn.call(this) ||
  1347. Boolean(this.options.d);
  1348. },
  1349. render: function (parent) {
  1350. var options = this.options,
  1351. attrs = this.attrsFromOptions(options);
  1352. this.graphic = this.annotation.chart.renderer
  1353. .path(['M', 0, 0])
  1354. .attr(attrs)
  1355. .add(parent);
  1356. if (options.className) {
  1357. this.graphic.addClass(options.className);
  1358. }
  1359. this.tracker = this.annotation.chart.renderer
  1360. .path(['M', 0, 0])
  1361. .addClass('highcharts-tracker-line')
  1362. .attr({
  1363. zIndex: 2
  1364. })
  1365. .add(parent);
  1366. if (!this.annotation.chart.styledMode) {
  1367. this.tracker.attr({
  1368. 'stroke-linejoin': 'round', // #1225
  1369. stroke: TRACKER_FILL,
  1370. fill: TRACKER_FILL,
  1371. 'stroke-width': this.graphic.strokeWidth() +
  1372. options.snap * 2
  1373. });
  1374. }
  1375. controllableMixin.render.call(this);
  1376. H.extend(this.graphic, {
  1377. markerStartSetter: markerMixin.markerStartSetter,
  1378. markerEndSetter: markerMixin.markerEndSetter
  1379. });
  1380. this.setMarkers(this);
  1381. },
  1382. redraw: function (animation) {
  1383. var d = this.toD(),
  1384. action = animation ? 'animate' : 'attr';
  1385. if (d) {
  1386. this.graphic[action]({ d: d });
  1387. this.tracker[action]({ d: d });
  1388. } else {
  1389. this.graphic.attr({ d: 'M 0 ' + -9e9 });
  1390. this.tracker.attr({ d: 'M 0 ' + -9e9 });
  1391. }
  1392. this.graphic.placed = this.tracker.placed = Boolean(d);
  1393. controllableMixin.redraw.call(this, animation);
  1394. }
  1395. }
  1396. );
  1397. return ControllablePath;
  1398. }(Highcharts, controllableMixin, markerMixin));
  1399. var ControllableRect = (function (H, controllableMixin, ControllablePath) {
  1400. /**
  1401. * A controllable rect class.
  1402. *
  1403. * @class
  1404. * @mixes Annotation.controllableMixin
  1405. * @memberOf Annotation
  1406. *
  1407. * @param {Highcharts.Annotation} annotation an annotation instance
  1408. * @param {Object} options a rect's options
  1409. * @param {number} index of the rectangle
  1410. **/
  1411. function ControllableRect(annotation, options, index) {
  1412. this.init(annotation, options, index);
  1413. this.collection = 'shapes';
  1414. }
  1415. /**
  1416. * @typedef {Annotation.ControllablePath.AttrsMap}
  1417. * Annotation.ControllableRect.AttrsMap
  1418. * @property {string} width=width
  1419. * @property {string} height=height
  1420. */
  1421. /**
  1422. * A map object which allows to map options attributes to element attributes
  1423. *
  1424. * @type {Annotation.ControllableRect.AttrsMap}
  1425. */
  1426. ControllableRect.attrsMap = H.merge(ControllablePath.attrsMap, {
  1427. width: 'width',
  1428. height: 'height'
  1429. });
  1430. H.merge(
  1431. true,
  1432. ControllableRect.prototype,
  1433. controllableMixin, /** @lends Annotation.ControllableRect# */ {
  1434. /**
  1435. * @type 'rect'
  1436. */
  1437. type: 'rect',
  1438. render: function (parent) {
  1439. var attrs = this.attrsFromOptions(this.options);
  1440. this.graphic = this.annotation.chart.renderer
  1441. .rect(0, -9e9, 0, 0)
  1442. .attr(attrs)
  1443. .add(parent);
  1444. controllableMixin.render.call(this);
  1445. },
  1446. redraw: function (animation) {
  1447. var position = this.anchor(this.points[0]).absolutePosition;
  1448. if (position) {
  1449. this.graphic[animation ? 'animate' : 'attr']({
  1450. x: position.x,
  1451. y: position.y,
  1452. width: this.options.width,
  1453. height: this.options.height
  1454. });
  1455. } else {
  1456. this.attr({
  1457. x: 0,
  1458. y: -9e9
  1459. });
  1460. }
  1461. this.graphic.placed = Boolean(position);
  1462. controllableMixin.redraw.call(this, animation);
  1463. },
  1464. translate: function (dx, dy) {
  1465. var annotationOptions = this.annotation.userOptions,
  1466. shapeOptions = annotationOptions[this.collection][this.index];
  1467. this.translatePoint(dx, dy, 0);
  1468. // Options stored in chart:
  1469. shapeOptions.point = this.options.point;
  1470. }
  1471. }
  1472. );
  1473. return ControllableRect;
  1474. }(Highcharts, controllableMixin, ControllablePath));
  1475. var ControllableCircle = (function (H, controllableMixin, ControllablePath) {
  1476. /**
  1477. * A controllable circle class.
  1478. *
  1479. * @constructor
  1480. * @mixes Annotation.controllableMixin
  1481. * @memberOf Annotation
  1482. *
  1483. * @param {Highcharts.Annotation} annotation an annotation instance
  1484. * @param {Object} options a shape's options
  1485. * @param {number} index of the circle
  1486. **/
  1487. function ControllableCircle(annotation, options, index) {
  1488. this.init(annotation, options, index);
  1489. this.collection = 'shapes';
  1490. }
  1491. /**
  1492. * A map object which allows to map options attributes to element attributes.
  1493. */
  1494. ControllableCircle.attrsMap = H.merge(ControllablePath.attrsMap, {
  1495. r: 'r'
  1496. });
  1497. H.merge(
  1498. true,
  1499. ControllableCircle.prototype,
  1500. controllableMixin, /** @lends Annotation.ControllableCircle# */ {
  1501. /**
  1502. * @type 'circle'
  1503. */
  1504. type: 'circle',
  1505. render: function (parent) {
  1506. var attrs = this.attrsFromOptions(this.options);
  1507. this.graphic = this.annotation.chart.renderer
  1508. .circle(0, -9e9, 0)
  1509. .attr(attrs)
  1510. .add(parent);
  1511. controllableMixin.render.call(this);
  1512. },
  1513. redraw: function (animation) {
  1514. var position = this.anchor(this.points[0]).absolutePosition;
  1515. if (position) {
  1516. this.graphic[animation ? 'animate' : 'attr']({
  1517. x: position.x,
  1518. y: position.y,
  1519. r: this.options.r
  1520. });
  1521. } else {
  1522. this.graphic.attr({
  1523. x: 0,
  1524. y: -9e9
  1525. });
  1526. }
  1527. this.graphic.placed = Boolean(position);
  1528. controllableMixin.redraw.call(this, animation);
  1529. },
  1530. translate: function (dx, dy) {
  1531. var annotationOptions = this.annotation.userOptions,
  1532. shapeOptions = annotationOptions[this.collection][this.index];
  1533. this.translatePoint(dx, dy, 0);
  1534. // Options stored in chart:
  1535. shapeOptions.point = this.options.point;
  1536. },
  1537. /**
  1538. * Set the radius.
  1539. *
  1540. * @param {number} r a radius to be set
  1541. */
  1542. setRadius: function (r) {
  1543. this.options.r = r;
  1544. }
  1545. }
  1546. );
  1547. return ControllableCircle;
  1548. }(Highcharts, controllableMixin, ControllablePath));
  1549. var ControllableLabel = (function (H, controllableMixin, MockPoint) {
  1550. /**
  1551. * A controllable label class.
  1552. *
  1553. * @class
  1554. * @mixes Annotation.controllableMixin
  1555. * @memberOf Annotation
  1556. *
  1557. * @param {Highcharts.Annotation} annotation an annotation instance
  1558. * @param {Object} options a label's options
  1559. * @param {number} index of the label
  1560. **/
  1561. function ControllableLabel(annotation, options, index) {
  1562. this.init(annotation, options, index);
  1563. this.collection = 'labels';
  1564. }
  1565. /**
  1566. * Shapes which do not have background - the object is used for proper
  1567. * setting of the contrast color.
  1568. *
  1569. * @type {Array<String>}
  1570. */
  1571. ControllableLabel.shapesWithoutBackground = ['connector'];
  1572. /**
  1573. * Returns new aligned position based alignment options and box to align to.
  1574. * It is almost a one-to-one copy from SVGElement.prototype.align
  1575. * except it does not use and mutate an element
  1576. *
  1577. * @param {Object} alignOptions
  1578. * @param {Object} box
  1579. * @return {Annotation.controllableMixin.Position} aligned position
  1580. */
  1581. ControllableLabel.alignedPosition = function (alignOptions, box) {
  1582. var align = alignOptions.align,
  1583. vAlign = alignOptions.verticalAlign,
  1584. x = (box.x || 0) + (alignOptions.x || 0),
  1585. y = (box.y || 0) + (alignOptions.y || 0),
  1586. alignFactor,
  1587. vAlignFactor;
  1588. if (align === 'right') {
  1589. alignFactor = 1;
  1590. } else if (align === 'center') {
  1591. alignFactor = 2;
  1592. }
  1593. if (alignFactor) {
  1594. x += (box.width - (alignOptions.width || 0)) / alignFactor;
  1595. }
  1596. if (vAlign === 'bottom') {
  1597. vAlignFactor = 1;
  1598. } else if (vAlign === 'middle') {
  1599. vAlignFactor = 2;
  1600. }
  1601. if (vAlignFactor) {
  1602. y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
  1603. }
  1604. return {
  1605. x: Math.round(x),
  1606. y: Math.round(y)
  1607. };
  1608. };
  1609. /**
  1610. * Returns new alignment options for a label if the label is outside the
  1611. * plot area. It is almost a one-to-one copy from
  1612. * Series.prototype.justifyDataLabel except it does not mutate the label and
  1613. * it works with absolute instead of relative position.
  1614. *
  1615. * @param {Object} label
  1616. * @param {Object} alignOptions
  1617. * @param {Object} alignAttr
  1618. * @return {Object} justified options
  1619. **/
  1620. ControllableLabel.justifiedOptions = function (
  1621. chart,
  1622. label,
  1623. alignOptions,
  1624. alignAttr
  1625. ) {
  1626. var align = alignOptions.align,
  1627. verticalAlign = alignOptions.verticalAlign,
  1628. padding = label.box ? 0 : (label.padding || 0),
  1629. bBox = label.getBBox(),
  1630. off,
  1631. options = {
  1632. align: align,
  1633. verticalAlign: verticalAlign,
  1634. x: alignOptions.x,
  1635. y: alignOptions.y,
  1636. width: label.width,
  1637. height: label.height
  1638. },
  1639. x = alignAttr.x - chart.plotLeft,
  1640. y = alignAttr.y - chart.plotTop;
  1641. // Off left
  1642. off = x + padding;
  1643. if (off < 0) {
  1644. if (align === 'right') {
  1645. options.align = 'left';
  1646. } else {
  1647. options.x = -off;
  1648. }
  1649. }
  1650. // Off right
  1651. off = x + bBox.width - padding;
  1652. if (off > chart.plotWidth) {
  1653. if (align === 'left') {
  1654. options.align = 'right';
  1655. } else {
  1656. options.x = chart.plotWidth - off;
  1657. }
  1658. }
  1659. // Off top
  1660. off = y + padding;
  1661. if (off < 0) {
  1662. if (verticalAlign === 'bottom') {
  1663. options.verticalAlign = 'top';
  1664. } else {
  1665. options.y = -off;
  1666. }
  1667. }
  1668. // Off bottom
  1669. off = y + bBox.height - padding;
  1670. if (off > chart.plotHeight) {
  1671. if (verticalAlign === 'top') {
  1672. options.verticalAlign = 'bottom';
  1673. } else {
  1674. options.y = chart.plotHeight - off;
  1675. }
  1676. }
  1677. return options;
  1678. };
  1679. /**
  1680. * @typedef {Object} Annotation.ControllableLabel.AttrsMap
  1681. * @property {string} backgroundColor=fill
  1682. * @property {string} borderColor=stroke
  1683. * @property {string} borderWidth=stroke-width
  1684. * @property {string} zIndex=zIndex
  1685. * @property {string} borderRadius=r
  1686. * @property {string} padding=padding
  1687. */
  1688. /**
  1689. * A map object which allows to map options attributes to element attributes
  1690. *
  1691. * @type {Annotation.ControllableLabel.AttrsMap}
  1692. */
  1693. ControllableLabel.attrsMap = {
  1694. backgroundColor: 'fill',
  1695. borderColor: 'stroke',
  1696. borderWidth: 'stroke-width',
  1697. zIndex: 'zIndex',
  1698. borderRadius: 'r',
  1699. padding: 'padding'
  1700. };
  1701. H.merge(
  1702. true,
  1703. ControllableLabel.prototype,
  1704. controllableMixin, /** @lends Annotation.ControllableLabel# */ {
  1705. /**
  1706. * Translate the point of the label by deltaX and deltaY translations.
  1707. * The point is the label's anchor.
  1708. *
  1709. * @param {number} dx translation for x coordinate
  1710. * @param {number} dy translation for y coordinate
  1711. **/
  1712. translatePoint: function (dx, dy) {
  1713. controllableMixin.translatePoint.call(this, dx, dy, 0);
  1714. },
  1715. /**
  1716. * Translate x and y position relative to the label's anchor.
  1717. *
  1718. * @param {number} dx translation for x coordinate
  1719. * @param {number} dy translation for y coordinate
  1720. **/
  1721. translate: function (dx, dy) {
  1722. var annotationOptions = this.annotation.userOptions,
  1723. labelOptions = annotationOptions[this.collection][this.index];
  1724. // Local options:
  1725. this.options.x += dx;
  1726. this.options.y += dy;
  1727. // Options stored in chart:
  1728. labelOptions.x = this.options.x;
  1729. labelOptions.y = this.options.y;
  1730. },
  1731. render: function (parent) {
  1732. var options = this.options,
  1733. attrs = this.attrsFromOptions(options),
  1734. style = options.style;
  1735. this.graphic = this.annotation.chart.renderer
  1736. .label(
  1737. '',
  1738. 0,
  1739. -9e9,
  1740. options.shape,
  1741. null,
  1742. null,
  1743. options.useHTML,
  1744. null,
  1745. 'annotation-label'
  1746. )
  1747. .attr(attrs)
  1748. .add(parent);
  1749. if (!this.annotation.chart.styledMode) {
  1750. if (style.color === 'contrast') {
  1751. style.color = this.annotation.chart.renderer.getContrast(
  1752. ControllableLabel.shapesWithoutBackground.indexOf(
  1753. options.shape
  1754. ) > -1 ? '#FFFFFF' : options.backgroundColor
  1755. );
  1756. }
  1757. this.graphic
  1758. .css(options.style)
  1759. .shadow(options.shadow);
  1760. }
  1761. if (options.className) {
  1762. this.graphic.addClass(options.className);
  1763. }
  1764. this.graphic.labelrank = options.labelrank;
  1765. controllableMixin.render.call(this);
  1766. },
  1767. redraw: function (animation) {
  1768. var options = this.options,
  1769. text = this.text || options.format || options.text,
  1770. label = this.graphic,
  1771. point = this.points[0],
  1772. show = false,
  1773. anchor,
  1774. attrs;
  1775. label.attr({
  1776. text: text ?
  1777. H.format(
  1778. text,
  1779. point.getLabelConfig(),
  1780. this.annotation.chart.time
  1781. ) :
  1782. options.formatter.call(point, this)
  1783. });
  1784. anchor = this.anchor(point);
  1785. attrs = this.position(anchor);
  1786. show = attrs;
  1787. if (show) {
  1788. label.alignAttr = attrs;
  1789. attrs.anchorX = anchor.absolutePosition.x;
  1790. attrs.anchorY = anchor.absolutePosition.y;
  1791. label[animation ? 'animate' : 'attr'](attrs);
  1792. } else {
  1793. label.attr({
  1794. x: 0,
  1795. y: -9e9
  1796. });
  1797. }
  1798. label.placed = Boolean(show);
  1799. controllableMixin.redraw.call(this, animation);
  1800. },
  1801. /**
  1802. * All basic shapes don't support alignTo() method except label.
  1803. * For a controllable label, we need to subtract translation from
  1804. * options.
  1805. */
  1806. anchor: function () {
  1807. var anchor = controllableMixin.anchor.apply(this, arguments),
  1808. x = this.options.x || 0,
  1809. y = this.options.y || 0;
  1810. anchor.absolutePosition.x -= x;
  1811. anchor.absolutePosition.y -= y;
  1812. anchor.relativePosition.x -= x;
  1813. anchor.relativePosition.y -= y;
  1814. return anchor;
  1815. },
  1816. /**
  1817. * Returns the label position relative to its anchor.
  1818. *
  1819. * @param {Annotation.controllableMixin.Anchor} anchor
  1820. * @return {Annotation.controllableMixin.Position|null} position
  1821. */
  1822. position: function (anchor) {
  1823. var item = this.graphic,
  1824. chart = this.annotation.chart,
  1825. point = this.points[0],
  1826. itemOptions = this.options,
  1827. anchorAbsolutePosition = anchor.absolutePosition,
  1828. anchorRelativePosition = anchor.relativePosition,
  1829. itemPosition,
  1830. alignTo,
  1831. itemPosRelativeX,
  1832. itemPosRelativeY,
  1833. showItem =
  1834. point.series.visible &&
  1835. MockPoint.prototype.isInsidePane.call(point);
  1836. if (showItem) {
  1837. if (itemOptions.distance) {
  1838. itemPosition = H.Tooltip.prototype.getPosition.call(
  1839. {
  1840. chart: chart,
  1841. distance: H.pick(itemOptions.distance, 16)
  1842. },
  1843. item.width,
  1844. item.height,
  1845. {
  1846. plotX: anchorRelativePosition.x,
  1847. plotY: anchorRelativePosition.y,
  1848. negative: point.negative,
  1849. ttBelow: point.ttBelow,
  1850. h: anchorRelativePosition.height ||
  1851. anchorRelativePosition.width
  1852. }
  1853. );
  1854. } else if (itemOptions.positioner) {
  1855. itemPosition = itemOptions.positioner.call(this);
  1856. } else {
  1857. alignTo = {
  1858. x: anchorAbsolutePosition.x,
  1859. y: anchorAbsolutePosition.y,
  1860. width: 0,
  1861. height: 0
  1862. };
  1863. itemPosition = ControllableLabel.alignedPosition(
  1864. H.extend(itemOptions, {
  1865. width: item.width,
  1866. height: item.height
  1867. }),
  1868. alignTo
  1869. );
  1870. if (this.options.overflow === 'justify') {
  1871. itemPosition = ControllableLabel.alignedPosition(
  1872. ControllableLabel.justifiedOptions(
  1873. chart,
  1874. item,
  1875. itemOptions,
  1876. itemPosition
  1877. ),
  1878. alignTo
  1879. );
  1880. }
  1881. }
  1882. if (itemOptions.crop) {
  1883. itemPosRelativeX = itemPosition.x - chart.plotLeft;
  1884. itemPosRelativeY = itemPosition.y - chart.plotTop;
  1885. showItem =
  1886. chart.isInsidePlot(
  1887. itemPosRelativeX,
  1888. itemPosRelativeY
  1889. ) &&
  1890. chart.isInsidePlot(
  1891. itemPosRelativeX + item.width,
  1892. itemPosRelativeY + item.height
  1893. );
  1894. }
  1895. }
  1896. return showItem ? itemPosition : null;
  1897. }
  1898. }
  1899. );
  1900. /* ********************************************************************** */
  1901. /**
  1902. * General symbol definition for labels with connector
  1903. */
  1904. H.SVGRenderer.prototype.symbols.connector = function (x, y, w, h, options) {
  1905. var anchorX = options && options.anchorX,
  1906. anchorY = options && options.anchorY,
  1907. path,
  1908. yOffset,
  1909. lateral = w / 2;
  1910. if (H.isNumber(anchorX) && H.isNumber(anchorY)) {
  1911. path = ['M', anchorX, anchorY];
  1912. // Prefer 45 deg connectors
  1913. yOffset = y - anchorY;
  1914. if (yOffset < 0) {
  1915. yOffset = -h - yOffset;
  1916. }
  1917. if (yOffset < w) {
  1918. lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;
  1919. }
  1920. // Anchor below label
  1921. if (anchorY > y + h) {
  1922. path.push('L', x + lateral, y + h);
  1923. // Anchor above label
  1924. } else if (anchorY < y) {
  1925. path.push('L', x + lateral, y);
  1926. // Anchor left of label
  1927. } else if (anchorX < x) {
  1928. path.push('L', x, y + h / 2);
  1929. // Anchor right of label
  1930. } else if (anchorX > x + w) {
  1931. path.push('L', x + w, y + h / 2);
  1932. }
  1933. }
  1934. return path || [];
  1935. };
  1936. return ControllableLabel;
  1937. }(Highcharts, controllableMixin, MockPoint));
  1938. var ControllableImage = (function (H, controllableMixin, ControllableLabel) {
  1939. /**
  1940. * A controllable image class.
  1941. *
  1942. * @class
  1943. * @mixes Annotation.controllableMixin
  1944. * @memberOf Annotation
  1945. *
  1946. * @param {Highcharts.Annotation} annotation - an annotation instance
  1947. * @param {Object} options a controllable's options
  1948. * @param {number} index of the image
  1949. **/
  1950. function ControllableImage(annotation, options, index) {
  1951. this.init(annotation, options, index);
  1952. this.collection = 'shapes';
  1953. }
  1954. /**
  1955. * @typedef {Object} Annotation.ControllableImage.AttrsMap
  1956. * @property {string} width=width
  1957. * @property {string} height=height
  1958. * @property {string} zIndex=zIndex
  1959. */
  1960. /**
  1961. * A map object which allows to map options attributes to element attributes
  1962. *
  1963. * @type {Annotation.ControllableImage.AttrsMap}
  1964. */
  1965. ControllableImage.attrsMap = {
  1966. width: 'width',
  1967. height: 'height',
  1968. zIndex: 'zIndex'
  1969. };
  1970. H.merge(
  1971. true,
  1972. ControllableImage.prototype,
  1973. controllableMixin, /** @lends Annotation.ControllableImage# */ {
  1974. /**
  1975. * @type 'image'
  1976. */
  1977. type: 'image',
  1978. render: function (parent) {
  1979. var attrs = this.attrsFromOptions(this.options),
  1980. options = this.options;
  1981. this.graphic = this.annotation.chart.renderer
  1982. .image(options.src, 0, -9e9, options.width, options.height)
  1983. .attr(attrs)
  1984. .add(parent);
  1985. this.graphic.width = options.width;
  1986. this.graphic.height = options.height;
  1987. controllableMixin.render.call(this);
  1988. },
  1989. redraw: function (animation) {
  1990. var anchor = this.anchor(this.points[0]),
  1991. position = ControllableLabel.prototype.position.call(
  1992. this,
  1993. anchor
  1994. );
  1995. if (position) {
  1996. this.graphic[animation ? 'animate' : 'attr']({
  1997. x: position.x,
  1998. y: position.y
  1999. });
  2000. } else {
  2001. this.graphic.attr({
  2002. x: 0,
  2003. y: -9e9
  2004. });
  2005. }
  2006. this.graphic.placed = Boolean(position);
  2007. controllableMixin.redraw.call(this, animation);
  2008. },
  2009. translate: function (dx, dy) {
  2010. var annotationOptions = this.annotation.userOptions,
  2011. shapeOptions = annotationOptions[this.collection][this.index];
  2012. this.translatePoint(dx, dy, 0);
  2013. // Options stored in chart:
  2014. shapeOptions.point = this.options.point;
  2015. }
  2016. }
  2017. );
  2018. return ControllableImage;
  2019. }(Highcharts, controllableMixin, ControllableLabel));
  2020. (function (H, controllableMixin, ControllableRect, ControllableCircle, ControllablePath, ControllableImage, ControllableLabel, eventEmitterMixin, MockPoint, ControlPoint) {
  2021. /**
  2022. * (c) 2009-2017 Highsoft, Black Label
  2023. *
  2024. * License: www.highcharts.com/license
  2025. */
  2026. var merge = H.merge,
  2027. addEvent = H.addEvent,
  2028. defined = H.defined,
  2029. erase = H.erase,
  2030. find = H.find,
  2031. isString = H.isString,
  2032. pick = H.pick,
  2033. reduce = H.reduce,
  2034. splat = H.splat,
  2035. destroyObjectProperties = H.destroyObjectProperties;
  2036. /* *********************************************************************
  2037. *
  2038. * ANNOTATION
  2039. *
  2040. ******************************************************************** */
  2041. /**
  2042. * @typedef {
  2043. * Annotation.ControllableCircle|
  2044. * Annotation.ControllableImage|
  2045. * Annotation.ControllablePath|
  2046. * Annotation.ControllableRect
  2047. * }
  2048. * Annotation.Shape
  2049. */
  2050. /**
  2051. * @typedef {Annotation.ControllableLabel} Annotation.Label
  2052. */
  2053. /**
  2054. * An annotation class which serves as a container for items like labels or
  2055. * shapes. Created items are positioned on the chart either by linking them to
  2056. * existing points or created mock points
  2057. *
  2058. * @class
  2059. * @mixes Annotation.controllableMixin
  2060. * @mixes Annotation.eventEmitterMixin
  2061. *
  2062. * @param {Highcharts.Chart} chart a chart instance
  2063. * @param {AnnotationOptions} options the options object
  2064. */
  2065. var Annotation = H.Annotation = function (chart, options) {
  2066. var labelsAndShapes;
  2067. /**
  2068. * The chart that the annotation belongs to.
  2069. *
  2070. * @type {Highcharts.Chart}
  2071. */
  2072. this.chart = chart;
  2073. /**
  2074. * The array of points which defines the annotation.
  2075. *
  2076. * @type {Array<Annotation.PointLike>}
  2077. */
  2078. this.points = [];
  2079. /**
  2080. * The array of control points.
  2081. *
  2082. * @type {Array<Annotation.ControlPoint>}
  2083. */
  2084. this.controlPoints = [];
  2085. this.coll = 'annotations';
  2086. /**
  2087. * The array of labels which belong to the annotation.
  2088. *
  2089. * @type {Array<Annotation.Label>}
  2090. */
  2091. this.labels = [];
  2092. /**
  2093. * The array of shapes which belong to the annotation.
  2094. *
  2095. * @type {Array<Annotation.Shape>}
  2096. */
  2097. this.shapes = [];
  2098. /**
  2099. * The options for the annotations.
  2100. *
  2101. * @type {AnnotationOptions}
  2102. */
  2103. // this.options = merge(this.defaultOptions, userOptions);
  2104. this.options = options;
  2105. /**
  2106. * The user options for the annotations.
  2107. *
  2108. * @type {AnnotationOptions}
  2109. */
  2110. this.userOptions = merge(true, {}, options);
  2111. // Handle labels and shapes - those are arrays
  2112. // Merging does not work with arrays (stores reference)
  2113. labelsAndShapes = this.getLabelsAndShapesOptions(
  2114. this.userOptions,
  2115. options
  2116. );
  2117. this.userOptions.labels = labelsAndShapes.labels;
  2118. this.userOptions.shapes = labelsAndShapes.shapes;
  2119. /**
  2120. * The callback that reports to the overlapping-labels module which
  2121. * labels it should account for.
  2122. *
  2123. * @name labelCollector
  2124. * @memberOf Annotation#
  2125. * @type {Function}
  2126. */
  2127. /**
  2128. * The group svg element.
  2129. *
  2130. * @name group
  2131. * @memberOf Annotation#
  2132. * @type {Highcharts.SVGElement}
  2133. */
  2134. /**
  2135. * The group svg element of the annotation's shapes.
  2136. *
  2137. * @name shapesGroup
  2138. * @memberOf Annotation#
  2139. * @type {Highcharts.SVGElement}
  2140. */
  2141. /**
  2142. * The group svg element of the annotation's labels.
  2143. *
  2144. * @name labelsGroup
  2145. * @memberOf Annotation#
  2146. * @type {Highcharts.SVGElement}
  2147. */
  2148. this.init(chart, options);
  2149. };
  2150. merge(
  2151. true,
  2152. Annotation.prototype,
  2153. controllableMixin,
  2154. eventEmitterMixin, /** @lends Annotation# */ {
  2155. /**
  2156. * A basic type of an annotation. It allows to add custom labels
  2157. * or shapes. The items can be tied to points, axis coordinates
  2158. * or chart pixel coordinates.
  2159. *
  2160. * @private
  2161. * @type {Object}
  2162. * @ignore-options base, annotations.crookedLine
  2163. * @sample highcharts/annotations/basic/
  2164. * Basic annotations
  2165. * @sample highcharts/demo/annotations/
  2166. * Advanced annotations
  2167. * @sample highcharts/css/annotations
  2168. * Styled mode
  2169. * @sample highcharts/annotations-advanced/controllable
  2170. * Controllable items
  2171. * @sample {highstock} stock/annotations/fibonacci-retracements
  2172. * Custom annotation, Fibonacci retracement
  2173. * @since 6.0.0
  2174. * @optionparent annotations.crookedLine
  2175. */
  2176. defaultOptions: {
  2177. /**
  2178. * Whether the annotation is visible.
  2179. *
  2180. * @sample highcharts/annotations/visible/
  2181. * Set annotation visibility
  2182. */
  2183. visible: true,
  2184. /**
  2185. * Allow an annotation to be draggable by a user. Possible
  2186. * values are `"x"`, `"xy"`, `"y"` and `""` (disabled).
  2187. *
  2188. * @type {string}
  2189. * @validvalue ["x", "xy", "y", ""]
  2190. */
  2191. draggable: 'xy',
  2192. /**
  2193. * Options for annotation's labels. Each label inherits options
  2194. * from the labelOptions object. An option from the labelOptions
  2195. * can be overwritten by config for a specific label.
  2196. */
  2197. labelOptions: {
  2198. /**
  2199. * The alignment of the annotation's label. If right,
  2200. * the right side of the label should be touching the point.
  2201. *
  2202. * @validvalue ["left", "center", "right"]
  2203. * @sample highcharts/annotations/label-position/
  2204. * Set labels position
  2205. */
  2206. align: 'center',
  2207. /**
  2208. * Whether to allow the annotation's labels to overlap.
  2209. * To make the labels less sensitive for overlapping,
  2210. * the can be set to 0.
  2211. *
  2212. * @sample highcharts/annotations/tooltip-like/
  2213. * Hide overlapping labels
  2214. */
  2215. allowOverlap: false,
  2216. /**
  2217. * The background color or gradient for the annotation's label.
  2218. *
  2219. * @type {Color}
  2220. * @sample highcharts/annotations/label-presentation/
  2221. * Set labels graphic options
  2222. */
  2223. backgroundColor: 'rgba(0, 0, 0, 0.75)',
  2224. /**
  2225. * The border color for the annotation's label.
  2226. *
  2227. * @type {Color}
  2228. * @sample highcharts/annotations/label-presentation/
  2229. * Set labels graphic options
  2230. */
  2231. borderColor: 'black',
  2232. /**
  2233. * The border radius in pixels for the annotaiton's label.
  2234. *
  2235. * @sample highcharts/annotations/label-presentation/
  2236. * Set labels graphic options
  2237. */
  2238. borderRadius: 3,
  2239. /**
  2240. * The border width in pixels for the annotation's label
  2241. *
  2242. * @sample highcharts/annotations/label-presentation/
  2243. * Set labels graphic options
  2244. */
  2245. borderWidth: 1,
  2246. /**
  2247. * A class name for styling by CSS.
  2248. *
  2249. * @sample highcharts/css/annotations
  2250. * Styled mode annotations
  2251. * @since 6.0.5
  2252. */
  2253. className: '',
  2254. /**
  2255. * Whether to hide the annotation's label
  2256. * that is outside the plot area.
  2257. *
  2258. * @sample highcharts/annotations/label-crop-overflow/
  2259. * Crop or justify labels
  2260. */
  2261. crop: false,
  2262. /**
  2263. * The label's pixel distance from the point.
  2264. *
  2265. * @type {number}
  2266. * @sample highcharts/annotations/label-position/
  2267. * Set labels position
  2268. * @default undefined
  2269. * @apioption annotations.crookedLine.labelOptions.distance
  2270. */
  2271. /**
  2272. * A [format](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) string for the data label.
  2273. *
  2274. * @type {string}
  2275. * @see [plotOptions.series.dataLabels.format](
  2276. * plotOptions.series.dataLabels.format.html)
  2277. * @sample highcharts/annotations/label-text/
  2278. * Set labels text
  2279. * @default undefined
  2280. * @apioption annotations.crookedLine.labelOptions.format
  2281. */
  2282. /**
  2283. * Alias for the format option.
  2284. *
  2285. * @type {string}
  2286. * @see [format](annotations.labelOptions.format.html)
  2287. * @sample highcharts/annotations/label-text/
  2288. * Set labels text
  2289. * @default undefined
  2290. * @apioption annotations.crookedLine.labelOptions.text
  2291. */
  2292. /**
  2293. * Callback JavaScript function to format
  2294. * the annotation's label. Note that if a `format` or `text`
  2295. * are defined, the format or text take precedence and
  2296. * the formatter is ignored. `This` refers to a * point object.
  2297. *
  2298. * @type {function}
  2299. * @sample highcharts/annotations/label-text/
  2300. * Set labels text
  2301. * @default function () {
  2302. * return defined(this.y) ? this.y : 'Annotation label';
  2303. * }
  2304. */
  2305. formatter: function () {
  2306. return defined(this.y) ? this.y : 'Annotation label';
  2307. },
  2308. /**
  2309. * How to handle the annotation's label that flow
  2310. * outside the plot area. The justify option aligns the label
  2311. * inside the plot area.
  2312. *
  2313. * @validvalue ["none", "justify"]
  2314. * @sample highcharts/annotations/label-crop-overflow/
  2315. * Crop or justify labels
  2316. **/
  2317. overflow: 'justify',
  2318. /**
  2319. * When either the borderWidth or the backgroundColor is set,
  2320. * this is the padding within the box.
  2321. *
  2322. * @sample highcharts/annotations/label-presentation/
  2323. * Set labels graphic options
  2324. */
  2325. padding: 5,
  2326. /**
  2327. * The shadow of the box. The shadow can be
  2328. * an object configuration containing
  2329. * `color`, `offsetX`, `offsetY`, `opacity` and `width`.
  2330. *
  2331. * @type {Boolean|Object}
  2332. * @sample highcharts/annotations/label-presentation/
  2333. * Set labels graphic options
  2334. */
  2335. shadow: false,
  2336. /**
  2337. * The name of a symbol to use for the border around the label.
  2338. * Symbols are predefined functions on the Renderer object.
  2339. *
  2340. * @type {string}
  2341. * @sample highcharts/annotations/shapes/
  2342. * Available shapes for labels
  2343. */
  2344. shape: 'callout',
  2345. /**
  2346. * Styles for the annotation's label.
  2347. *
  2348. * @type {CSSObject}
  2349. * @sample highcharts/annotations/label-presentation/
  2350. * Set labels graphic options
  2351. * @see [plotOptions.series.dataLabels.style](
  2352. * plotOptions.series.dataLabels.style.html)
  2353. */
  2354. style: {
  2355. fontSize: '11px',
  2356. fontWeight: 'normal',
  2357. color: 'contrast'
  2358. },
  2359. /**
  2360. * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
  2361. * to render the annotation's label.
  2362. *
  2363. * @type {boolean}
  2364. * @default false
  2365. */
  2366. useHTML: false,
  2367. /**
  2368. * The vertical alignment of the annotation's label.
  2369. *
  2370. * @type {string}
  2371. * @validvalue ["top", "middle", "bottom"]
  2372. * @sample highcharts/annotations/label-position/
  2373. * Set labels position
  2374. */
  2375. verticalAlign: 'bottom',
  2376. /**
  2377. * The x position offset of the label relative to the point.
  2378. * Note that if a `distance` is defined, the distance takes
  2379. * precedence over `x` and `y` options.
  2380. *
  2381. * @sample highcharts/annotations/label-position/
  2382. * Set labels position
  2383. */
  2384. x: 0,
  2385. /**
  2386. * The y position offset of the label relative to the point.
  2387. * Note that if a `distance` is defined, the distance takes
  2388. * precedence over `x` and `y` options.
  2389. *
  2390. * @sample highcharts/annotations/label-position/
  2391. * Set labels position
  2392. */
  2393. y: -16
  2394. },
  2395. /**
  2396. * An array of labels for the annotation. For options that apply to
  2397. * multiple labels, they can be added to the
  2398. * [labelOptions](annotations.labelOptions.html).
  2399. *
  2400. * @type {Array<Object>}
  2401. * @extends annotations.crookedLine.labelOptions
  2402. * @apioption annotations.crookedLine.labels
  2403. */
  2404. /**
  2405. * This option defines the point to which the label
  2406. * will be connected.
  2407. * It can be either the point which exists in the series - it is
  2408. * referenced by the point's id - or a new point with defined x, y
  2409. * properies and optionally axes.
  2410. *
  2411. * @type {string|MockPointOptions}
  2412. * @sample highcharts/annotations/mock-point/
  2413. * Attach annotation to a mock point
  2414. * @apioption annotations.crookedLine.labels.point
  2415. */
  2416. /**
  2417. * The x position of the point. Units can be either in axis
  2418. * or chart pixel coordinates.
  2419. *
  2420. * @type {number}
  2421. * @apioption annotations.crookedLine.labels.point.x
  2422. */
  2423. /**
  2424. * The y position of the point. Units can be either in axis
  2425. * or chart pixel coordinates.
  2426. *
  2427. * @type {number}
  2428. * @apioption annotations.crookedLine.labels.point.y
  2429. */
  2430. /**
  2431. * This number defines which xAxis the point is connected to.
  2432. * It refers to either the axis id or the index of the axis
  2433. * in the xAxis array. If the option is not configured or
  2434. * the axis is not found the point's
  2435. * x coordinate refers to the chart pixels.
  2436. *
  2437. * @type {number|string}
  2438. * @apioption annotations.crookedLine.labels.point.xAxis
  2439. */
  2440. /**
  2441. * This number defines which yAxis the point is connected to.
  2442. * It refers to either the axis id or the index of the axis
  2443. * in the yAxis array. If the option is not configured or
  2444. * the axis is not found the point's
  2445. * y coordinate refers to the chart pixels.
  2446. *
  2447. * @type {number|string}
  2448. * @apioption annotations.crookedLine.labels.point.yAxis
  2449. */
  2450. /**
  2451. * An array of shapes for the annotation. For options that apply to
  2452. * multiple shapes, then can be added to the
  2453. * [shapeOptions](annotations.shapeOptions.html).
  2454. *
  2455. * @type {Array<Object>}
  2456. * @extends annotations.crookedLine.shapeOptions
  2457. * @apioption annotations.crookedLine.shapes
  2458. */
  2459. /**
  2460. * This option defines the point to which the shape will be
  2461. * connected.
  2462. * It can be either the point which exists in the series - it is
  2463. * referenced by the point's id - or a new point with defined x, y
  2464. * properties and optionally axes.
  2465. *
  2466. * @type {string|MockPointOptions}
  2467. * @extends annotations.crookedLine.labels.point
  2468. * @apioption annotations.crookedLine.shapes.point
  2469. */
  2470. /**
  2471. * An array of points for the shape. This option is available
  2472. * for shapes which can use multiple points such as path.
  2473. * A point can be either a point object or a point's id.
  2474. *
  2475. * @type {Array<string|Highcharts.MockPoint.Options>}
  2476. * @see [annotations.shapes.point](annotations.shapes.point.html)
  2477. * @apioption annotations.crookedLine.shapes.points
  2478. */
  2479. /**
  2480. * Id of the marker which will be drawn at the final
  2481. * vertex of the path.
  2482. * Custom markers can be defined in defs property.
  2483. *
  2484. * @type {string}
  2485. * @see [defs.markers](defs.markers.html)
  2486. * @sample highcharts/annotations/custom-markers/
  2487. * Define a custom marker for annotations
  2488. * @apioption annotations.crookedLine.shapes.markerEnd
  2489. */
  2490. /**
  2491. * Id of the marker which will be drawn at the first
  2492. * vertex of the path.
  2493. * Custom markers can be defined in defs property.
  2494. *
  2495. * @type {string}
  2496. * @see [defs.markers](defs.markers.html)
  2497. * @sample {highcharts} highcharts/annotations/custom-markers/
  2498. * Define a custom marker for annotations
  2499. * @apioption annotations.crookedLine.shapes.markerStart
  2500. */
  2501. /**
  2502. * Options for annotation's shapes. Each shape inherits options
  2503. * from the shapeOptions object. An option from the shapeOptions
  2504. * can be overwritten by config for a specific shape.
  2505. *
  2506. * @type {Object}
  2507. */
  2508. shapeOptions: {
  2509. /**
  2510. * The width of the shape.
  2511. *
  2512. * @type {number}
  2513. * @sample highcharts/annotations/shape/
  2514. * Basic shape annotation
  2515. * @apioption annotations.crookedLine.shapeOptions.width
  2516. **/
  2517. /**
  2518. * The height of the shape.
  2519. *
  2520. * @type {number}
  2521. * @sample highcharts/annotations/shape/
  2522. * Basic shape annotation
  2523. * @apioption annotations.crookedLine.shapeOptions.height
  2524. */
  2525. /**
  2526. * The color of the shape's stroke.
  2527. *
  2528. * @type {Color}
  2529. * @sample highcharts/annotations/shape/
  2530. * Basic shape annotation
  2531. */
  2532. stroke: 'rgba(0, 0, 0, 0.75)',
  2533. /**
  2534. * The pixel stroke width of the shape.
  2535. *
  2536. * @sample highcharts/annotations/shape/
  2537. * Basic shape annotation
  2538. */
  2539. strokeWidth: 1,
  2540. /**
  2541. * The color of the shape's fill.
  2542. *
  2543. * @type {Color}
  2544. * @sample highcharts/annotations/shape/
  2545. * Basic shape annotation
  2546. */
  2547. fill: 'rgba(0, 0, 0, 0.75)',
  2548. /**
  2549. * The type of the shape, e.g. circle or rectangle.
  2550. *
  2551. * @type {string}
  2552. * @sample highcharts/annotations/shape/
  2553. * Basic shape annotation
  2554. * @default 'rect'
  2555. * @apioption annotations.crookedLine.shapeOptions.type
  2556. */
  2557. /**
  2558. * The radius of the shape.
  2559. *
  2560. * @sample highcharts/annotations/shape/
  2561. * Basic shape annotation
  2562. */
  2563. r: 0,
  2564. /**
  2565. * Defines additional snapping area around an annotation
  2566. * making this annotation to focus. Defined in pixels.
  2567. */
  2568. snap: 2
  2569. },
  2570. /**
  2571. * Options for annotation's control points. Each control point
  2572. * inherits options from controlPointOptions object.
  2573. * Options from the controlPointOptions can be overwritten
  2574. * by options in a specific control point.
  2575. *
  2576. * @type {Annotation.ControlPoint.Options}
  2577. * @apioption annotations.crookedLine.controlPointOptions
  2578. */
  2579. controlPointOptions: {
  2580. symbol: 'circle',
  2581. width: 10,
  2582. height: 10,
  2583. style: {
  2584. stroke: 'black',
  2585. 'stroke-width': 2,
  2586. fill: 'white'
  2587. },
  2588. visible: false,
  2589. /**
  2590. * @function {Annotation.ControlPoint.Positioner}
  2591. * @apioption annotations.crookedLine.controlPointOptions.positioner
  2592. */
  2593. events: {}
  2594. },
  2595. /**
  2596. * @type {Object}
  2597. */
  2598. events: {},
  2599. /**
  2600. * The Z index of the annotation.
  2601. *
  2602. * @type {number}
  2603. * @default 6
  2604. */
  2605. zIndex: 6
  2606. },
  2607. /**
  2608. * Initialize the annotation.
  2609. *
  2610. * @param {Highcharts.Chart} - the chart
  2611. * @param {AnnotationOptions} - the user options for the annotation
  2612. */
  2613. init: function () {
  2614. this.linkPoints();
  2615. this.addControlPoints();
  2616. this.addShapes();
  2617. this.addLabels();
  2618. this.addClipPaths();
  2619. this.setLabelCollector();
  2620. },
  2621. getLabelsAndShapesOptions: function (baseOptions, newOptions) {
  2622. var mergedOptions = {};
  2623. ['labels', 'shapes'].forEach(function (name) {
  2624. if (baseOptions[name]) {
  2625. mergedOptions[name] = splat(newOptions[name]).map(
  2626. function (basicOptions, i) {
  2627. return merge(baseOptions[name][i], basicOptions);
  2628. }
  2629. );
  2630. }
  2631. });
  2632. return mergedOptions;
  2633. },
  2634. addShapes: function () {
  2635. (this.options.shapes || []).forEach(function (shapeOptions, i) {
  2636. var shape = this.initShape(shapeOptions, i);
  2637. this.options.shapes[i] = shape.options;
  2638. }, this);
  2639. },
  2640. addLabels: function () {
  2641. (this.options.labels || []).forEach(function (labelOptions, i) {
  2642. var label = this.initLabel(labelOptions, i);
  2643. this.options.labels[i] = label.options;
  2644. }, this);
  2645. },
  2646. addClipPaths: function () {
  2647. this.setClipAxes();
  2648. if (this.clipXAxis && this.clipYAxis) {
  2649. this.clipRect = this.chart.renderer.clipRect(
  2650. this.getClipBox()
  2651. );
  2652. }
  2653. },
  2654. setClipAxes: function () {
  2655. var xAxes = this.chart.xAxis,
  2656. yAxes = this.chart.yAxis,
  2657. linkedAxes = reduce(
  2658. (this.options.labels || [])
  2659. .concat(this.options.shapes || []),
  2660. function (axes, labelOrShape) {
  2661. return [
  2662. xAxes[
  2663. labelOrShape &&
  2664. labelOrShape.point &&
  2665. labelOrShape.point.xAxis
  2666. ] || axes[0],
  2667. yAxes[
  2668. labelOrShape &&
  2669. labelOrShape.point &&
  2670. labelOrShape.point.yAxis
  2671. ] || axes[1]
  2672. ];
  2673. },
  2674. []
  2675. );
  2676. this.clipXAxis = linkedAxes[0];
  2677. this.clipYAxis = linkedAxes[1];
  2678. },
  2679. getClipBox: function () {
  2680. return {
  2681. x: this.clipXAxis.left,
  2682. y: this.clipYAxis.top,
  2683. width: this.clipXAxis.width,
  2684. height: this.clipYAxis.height
  2685. };
  2686. },
  2687. setLabelCollector: function () {
  2688. var annotation = this;
  2689. annotation.labelCollector = function () {
  2690. return annotation.labels.reduce(
  2691. function (labels, label) {
  2692. if (!label.options.allowOverlap) {
  2693. labels.push(label.graphic);
  2694. }
  2695. return labels;
  2696. },
  2697. []
  2698. );
  2699. };
  2700. annotation.chart.labelCollectors.push(
  2701. annotation.labelCollector
  2702. );
  2703. },
  2704. /**
  2705. * Set an annotation options.
  2706. *
  2707. * @param {AnnotationOptions} - user options for an annotation
  2708. */
  2709. setOptions: function (userOptions) {
  2710. this.options = merge(this.defaultOptions, userOptions);
  2711. },
  2712. redraw: function (animation) {
  2713. this.linkPoints();
  2714. if (!this.graphic) {
  2715. this.render();
  2716. }
  2717. if (this.clipRect) {
  2718. this.clipRect.animate(this.getClipBox());
  2719. }
  2720. this.redrawItems(this.shapes, animation);
  2721. this.redrawItems(this.labels, animation);
  2722. controllableMixin.redraw.call(this, animation);
  2723. },
  2724. /**
  2725. * @param {Array<(Annotation.Label|Annotation.Shape)>} items
  2726. * @param {boolean} [animation]
  2727. */
  2728. redrawItems: function (items, animation) {
  2729. var i = items.length;
  2730. // needs a backward loop
  2731. // labels/shapes array might be modified
  2732. // due to destruction of the item
  2733. while (i--) {
  2734. this.redrawItem(items[i], animation);
  2735. }
  2736. },
  2737. render: function () {
  2738. var renderer = this.chart.renderer;
  2739. this.graphic = renderer
  2740. .g('annotation')
  2741. .attr({
  2742. zIndex: this.options.zIndex,
  2743. visibility: this.options.visible ?
  2744. 'visible' :
  2745. 'hidden'
  2746. })
  2747. .add();
  2748. this.shapesGroup = renderer
  2749. .g('annotation-shapes')
  2750. .add(this.graphic)
  2751. .clip(this.chart.plotBoxClip);
  2752. this.labelsGroup = renderer
  2753. .g('annotation-labels')
  2754. .attr({
  2755. // hideOverlappingLabels requires translation
  2756. translateX: 0,
  2757. translateY: 0
  2758. })
  2759. .add(this.graphic);
  2760. if (this.clipRect) {
  2761. this.graphic.clip(this.clipRect);
  2762. }
  2763. this.addEvents();
  2764. controllableMixin.render.call(this);
  2765. },
  2766. /**
  2767. * Set the annotation's visibility.
  2768. *
  2769. * @param {Boolean} [visible] - Whether to show or hide an annotation.
  2770. * If the param is omitted, the annotation's visibility is toggled.
  2771. */
  2772. setVisibility: function (visibility) {
  2773. var options = this.options,
  2774. visible = pick(visibility, !options.visible);
  2775. this.graphic.attr(
  2776. 'visibility',
  2777. visible ? 'visible' : 'hidden'
  2778. );
  2779. if (!visible) {
  2780. this.setControlPointsVisibility(false);
  2781. }
  2782. options.visible = visible;
  2783. },
  2784. setControlPointsVisibility: function (visible) {
  2785. var setItemControlPointsVisibility = function (item) {
  2786. item.setControlPointsVisibility(visible);
  2787. };
  2788. controllableMixin.setControlPointsVisibility.call(
  2789. this,
  2790. visible
  2791. );
  2792. this.shapes.forEach(setItemControlPointsVisibility);
  2793. this.labels.forEach(setItemControlPointsVisibility);
  2794. },
  2795. /**
  2796. * Destroy the annotation. This function does not touch the chart
  2797. * that the annotation belongs to (all annotations are kept in
  2798. * the chart.annotations array) - it is recommended to use
  2799. * {@link Highcharts.Chart#removeAnnotation} instead.
  2800. */
  2801. destroy: function () {
  2802. var chart = this.chart,
  2803. destroyItem = function (item) {
  2804. item.destroy();
  2805. };
  2806. this.labels.forEach(destroyItem);
  2807. this.shapes.forEach(destroyItem);
  2808. this.clipXAxis = null;
  2809. this.clipYAxis = null;
  2810. erase(chart.labelCollectors, this.labelCollector);
  2811. eventEmitterMixin.destroy.call(this);
  2812. controllableMixin.destroy.call(this);
  2813. destroyObjectProperties(this, chart);
  2814. },
  2815. /**
  2816. * See {@link Highcharts.Annotation#destroy}.
  2817. */
  2818. remove: function () {
  2819. return this.destroy();
  2820. },
  2821. update: function (userOptions) {
  2822. var chart = this.chart,
  2823. labelsAndShapes = this.getLabelsAndShapesOptions(
  2824. this.userOptions,
  2825. userOptions
  2826. ),
  2827. userOptionsIndex = chart.annotations.indexOf(this),
  2828. options = H.merge(true, this.userOptions, userOptions);
  2829. options.labels = labelsAndShapes.labels;
  2830. options.shapes = labelsAndShapes.shapes;
  2831. this.destroy();
  2832. this.constructor(chart, options);
  2833. // Update options in chart options, used in exporting (#9767):
  2834. chart.options.annotations[userOptionsIndex] = options;
  2835. this.redraw();
  2836. },
  2837. /* *************************************************************
  2838. * ITEM SECTION
  2839. * Contains methods for handling a single item in an annotation
  2840. **************************************************************** */
  2841. /**
  2842. * Initialisation of a single shape
  2843. *
  2844. * @param {Object} shapeOptions - a confg object for a single shape
  2845. **/
  2846. initShape: function (shapeOptions, index) {
  2847. var options = merge(
  2848. this.options.shapeOptions,
  2849. {
  2850. controlPointOptions: this.options.controlPointOptions
  2851. },
  2852. shapeOptions
  2853. ),
  2854. shape = new Annotation.shapesMap[options.type](
  2855. this,
  2856. options,
  2857. index
  2858. );
  2859. shape.itemType = 'shape';
  2860. this.shapes.push(shape);
  2861. return shape;
  2862. },
  2863. /**
  2864. * Initialisation of a single label
  2865. *
  2866. * @param {Object} labelOptions
  2867. **/
  2868. initLabel: function (labelOptions, index) {
  2869. var options = merge(
  2870. this.options.labelOptions,
  2871. {
  2872. controlPointOptions: this.options.controlPointOptions
  2873. },
  2874. labelOptions
  2875. ),
  2876. label = new ControllableLabel(
  2877. this,
  2878. options,
  2879. index
  2880. );
  2881. label.itemType = 'label';
  2882. this.labels.push(label);
  2883. return label;
  2884. },
  2885. /**
  2886. * Redraw a single item.
  2887. *
  2888. * @param {Annotation.Label|Annotation.Shape} item
  2889. * @param {boolean} [animation]
  2890. */
  2891. redrawItem: function (item, animation) {
  2892. item.linkPoints();
  2893. if (!item.shouldBeDrawn()) {
  2894. this.destroyItem(item);
  2895. } else {
  2896. if (!item.graphic) {
  2897. this.renderItem(item);
  2898. }
  2899. item.redraw(
  2900. H.pick(animation, true) && item.graphic.placed
  2901. );
  2902. if (item.points.length) {
  2903. this.adjustVisibility(item);
  2904. }
  2905. }
  2906. },
  2907. /**
  2908. * Hide or show annotaiton attached to points.
  2909. *
  2910. * @param {Annotation.Label|Annotation.Shape} item
  2911. */
  2912. adjustVisibility: function (item) { // #9481
  2913. var hasVisiblePoints = false,
  2914. label = item.graphic;
  2915. item.points.forEach(function (point) {
  2916. if (
  2917. point.series.visible !== false &&
  2918. point.visible !== false
  2919. ) {
  2920. hasVisiblePoints = true;
  2921. }
  2922. });
  2923. if (!hasVisiblePoints) {
  2924. label.hide();
  2925. } else if (label.visibility === 'hidden') {
  2926. label.show();
  2927. }
  2928. },
  2929. /**
  2930. * Destroy a single item.
  2931. *
  2932. * @param {Annotation.Label|Annotation.Shape} item
  2933. */
  2934. destroyItem: function (item) {
  2935. // erase from shapes or labels array
  2936. erase(this[item.itemType + 's'], item);
  2937. item.destroy();
  2938. },
  2939. /*
  2940. * @private
  2941. */
  2942. renderItem: function (item) {
  2943. item.render(
  2944. item.itemType === 'label' ?
  2945. this.labelsGroup :
  2946. this.shapesGroup
  2947. );
  2948. }
  2949. }
  2950. );
  2951. /**
  2952. * An object uses for mapping between a shape type and a constructor.
  2953. * To add a new shape type extend this object with type name as a key
  2954. * and a constructor as its value.
  2955. **/
  2956. Annotation.shapesMap = {
  2957. 'rect': ControllableRect,
  2958. 'circle': ControllableCircle,
  2959. 'path': ControllablePath,
  2960. 'image': ControllableImage
  2961. };
  2962. Annotation.types = {};
  2963. Annotation.MockPoint = MockPoint;
  2964. Annotation.ControlPoint = ControlPoint;
  2965. H.extendAnnotation = function (
  2966. Constructor,
  2967. BaseConstructor,
  2968. prototype,
  2969. defaultOptions
  2970. ) {
  2971. BaseConstructor = BaseConstructor || Annotation;
  2972. merge(
  2973. true,
  2974. Constructor.prototype,
  2975. BaseConstructor.prototype,
  2976. prototype
  2977. );
  2978. Constructor.prototype.defaultOptions = merge(
  2979. Constructor.prototype.defaultOptions,
  2980. defaultOptions || {}
  2981. );
  2982. };
  2983. /* *********************************************************************
  2984. *
  2985. * EXTENDING CHART PROTOTYPE
  2986. *
  2987. ******************************************************************** */
  2988. // Let chart.update() work with annotations
  2989. H.Chart.prototype.collectionsWithUpdate.push('annotations');
  2990. H.extend(H.Chart.prototype, /** @lends Highcharts.Chart# */ {
  2991. initAnnotation: function (userOptions) {
  2992. var Constructor =
  2993. Annotation.types[userOptions.type] || Annotation,
  2994. options = H.merge(
  2995. Constructor.prototype.defaultOptions,
  2996. userOptions
  2997. ),
  2998. annotation = new Constructor(this, options);
  2999. this.annotations.push(annotation);
  3000. return annotation;
  3001. },
  3002. /**
  3003. * Add an annotation to the chart after render time.
  3004. *
  3005. * @param {AnnotationOptions} options
  3006. * The annotation options for the new, detailed annotation.
  3007. * @param {boolean} [redraw]
  3008. *
  3009. * @return {Highcharts.Annotation} - The newly generated annotation.
  3010. */
  3011. addAnnotation: function (userOptions, redraw) {
  3012. var annotation = this.initAnnotation(userOptions);
  3013. this.options.annotations.push(annotation.options);
  3014. if (pick(redraw, true)) {
  3015. annotation.redraw();
  3016. }
  3017. return annotation;
  3018. },
  3019. /**
  3020. * Remove an annotation from the chart.
  3021. *
  3022. * @param {String|Annotation} idOrAnnotation - The annotation's id or
  3023. * direct annotation object.
  3024. */
  3025. removeAnnotation: function (idOrAnnotation) {
  3026. var annotations = this.annotations,
  3027. annotation = isString(idOrAnnotation) ? find(
  3028. annotations,
  3029. function (annotation) {
  3030. return annotation.options.id === idOrAnnotation;
  3031. }
  3032. ) : idOrAnnotation;
  3033. if (annotation) {
  3034. erase(this.options.annotations, annotation.options);
  3035. erase(annotations, annotation);
  3036. annotation.destroy();
  3037. }
  3038. },
  3039. drawAnnotations: function () {
  3040. this.plotBoxClip.attr(this.plotBox);
  3041. this.annotations.forEach(function (annotation) {
  3042. annotation.redraw();
  3043. });
  3044. }
  3045. });
  3046. H.Chart.prototype.callbacks.push(function (chart) {
  3047. chart.annotations = [];
  3048. if (!chart.options.annotations) {
  3049. chart.options.annotations = [];
  3050. }
  3051. chart.plotBoxClip = this.renderer.clipRect(this.plotBox);
  3052. chart.controlPointsGroup = chart.renderer
  3053. .g('control-points')
  3054. .attr({ zIndex: 99 })
  3055. .clip(chart.plotBoxClip)
  3056. .add();
  3057. chart.options.annotations.forEach(function (annotationOptions, i) {
  3058. var annotation = chart.initAnnotation(annotationOptions);
  3059. chart.options.annotations[i] = annotation.options;
  3060. });
  3061. chart.drawAnnotations();
  3062. addEvent(chart, 'redraw', chart.drawAnnotations);
  3063. addEvent(chart, 'destroy', function () {
  3064. chart.plotBoxClip.destroy();
  3065. chart.controlPointsGroup.destroy();
  3066. });
  3067. });
  3068. }(Highcharts, controllableMixin, ControllableRect, ControllableCircle, ControllablePath, ControllableImage, ControllableLabel, eventEmitterMixin, MockPoint, ControlPoint));
  3069. var CrookedLine = (function (H) {
  3070. var Annotation = H.Annotation,
  3071. MockPoint = Annotation.MockPoint,
  3072. ControlPoint = Annotation.ControlPoint;
  3073. /**
  3074. * @class
  3075. * @extends Annotation
  3076. * @memberOf Annotation
  3077. */
  3078. function CrookedLine() {
  3079. Annotation.apply(this, arguments);
  3080. }
  3081. H.extendAnnotation(
  3082. CrookedLine,
  3083. null,
  3084. /** @lends Annotation.CrookedLine# */
  3085. {
  3086. /**
  3087. * Overrides default setter to get axes from typeOptions.
  3088. */
  3089. setClipAxes: function () {
  3090. this.clipXAxis = this.chart.xAxis[this.options.typeOptions.xAxis];
  3091. this.clipYAxis = this.chart.yAxis[this.options.typeOptions.yAxis];
  3092. },
  3093. getPointsOptions: function () {
  3094. var typeOptions = this.options.typeOptions;
  3095. return typeOptions.points.map(function (pointOptions) {
  3096. pointOptions.xAxis = typeOptions.xAxis;
  3097. pointOptions.yAxis = typeOptions.yAxis;
  3098. return pointOptions;
  3099. });
  3100. },
  3101. getControlPointsOptions: function () {
  3102. return this.getPointsOptions();
  3103. },
  3104. addControlPoints: function () {
  3105. this.getControlPointsOptions().forEach(
  3106. function (pointOptions, i) {
  3107. var controlPoint = new ControlPoint(
  3108. this.chart,
  3109. this,
  3110. H.merge(
  3111. this.options.controlPointOptions,
  3112. pointOptions.controlPoint
  3113. ),
  3114. i
  3115. );
  3116. this.controlPoints.push(controlPoint);
  3117. pointOptions.controlPoint = controlPoint.options;
  3118. },
  3119. this
  3120. );
  3121. },
  3122. addShapes: function () {
  3123. var typeOptions = this.options.typeOptions,
  3124. shape = this.initShape(
  3125. H.merge(typeOptions.line, {
  3126. type: 'path',
  3127. points: this.points.map(function (point, i) {
  3128. return function (target) {
  3129. return target.annotation.points[i];
  3130. };
  3131. })
  3132. }),
  3133. false
  3134. );
  3135. typeOptions.line = shape.options;
  3136. }
  3137. },
  3138. /**
  3139. * A crooked line annotation.
  3140. *
  3141. * @excluding labels, shapes
  3142. * @sample highcharts/annotations-advanced/crooked-line/
  3143. * Crooked line
  3144. * @product highstock
  3145. * @optionparent annotations.crookedLine
  3146. */
  3147. {
  3148. /**
  3149. * Additional options for an annotation with the type.
  3150. */
  3151. typeOptions: {
  3152. /**
  3153. * This number defines which xAxis the point is connected to.
  3154. * It refers to either the axis id or the index of the axis
  3155. * in the xAxis array.
  3156. */
  3157. xAxis: 0,
  3158. /**
  3159. * This number defines which yAxis the point is connected to.
  3160. * It refers to either the axis id or the index of the axis
  3161. * in the xAxis array.
  3162. */
  3163. yAxis: 0,
  3164. /**
  3165. * @type {Array<Object>}
  3166. * @apioption annotations.crookedLine.typeOptions.points
  3167. */
  3168. /**
  3169. * The x position of the point.
  3170. * @type {number}
  3171. * @apioption annotations.crookedLine.typeOptions.points.x
  3172. */
  3173. /**
  3174. * The y position of the point.
  3175. * @type {number}
  3176. * @apioption annotations.crookedLine.typeOptions.points.y
  3177. */
  3178. /**
  3179. * @type {number}
  3180. * @excluding positioner, events
  3181. * @apioption annotations.crookedLine.typeOptions.points.controlPoint
  3182. */
  3183. /**
  3184. * Line options.
  3185. *
  3186. * @type {Object}
  3187. * @excluding height, point, points, r, type, width
  3188. */
  3189. line: {
  3190. fill: 'none'
  3191. }
  3192. },
  3193. /**
  3194. * @excluding positioner, events
  3195. */
  3196. controlPointOptions: {
  3197. positioner: function (target) {
  3198. var graphic = this.graphic,
  3199. xy = MockPoint.pointToPixels(target.points[this.index]);
  3200. return {
  3201. x: xy.x - graphic.width / 2,
  3202. y: xy.y - graphic.height / 2
  3203. };
  3204. },
  3205. events: {
  3206. drag: function (e, target) {
  3207. if (
  3208. target.chart.isInsidePlot(
  3209. e.chartX - target.chart.plotLeft,
  3210. e.chartY - target.chart.plotTop
  3211. )
  3212. ) {
  3213. var translation = this.mouseMoveToTranslation(e);
  3214. target.translatePoint(
  3215. translation.x,
  3216. translation.y,
  3217. this.index
  3218. );
  3219. // Update options:
  3220. target.options.typeOptions.points[this.index].x =
  3221. target.points[this.index].x;
  3222. target.options.typeOptions.points[this.index].y =
  3223. target.points[this.index].y;
  3224. target.redraw(false);
  3225. }
  3226. }
  3227. }
  3228. }
  3229. }
  3230. );
  3231. Annotation.types.crookedLine = CrookedLine;
  3232. return CrookedLine;
  3233. }(Highcharts));
  3234. var ElliottWave = (function (H) {
  3235. var Annotation = H.Annotation,
  3236. CrookedLine = Annotation.types.crookedLine;
  3237. /**
  3238. * @class
  3239. * @extends Annotation.CrookedLine
  3240. * @memberOf Annotation
  3241. */
  3242. function ElliottWave() {
  3243. CrookedLine.apply(this, arguments);
  3244. }
  3245. H.extendAnnotation(ElliottWave, CrookedLine,
  3246. /** Annotation.CrookedLine# */
  3247. {
  3248. addLabels: function () {
  3249. this.getPointsOptions().forEach(function (point, i) {
  3250. var label = this.initLabel(H.merge(
  3251. point.label, {
  3252. text: this.options.typeOptions.labels[i],
  3253. point: function (target) {
  3254. return target.annotation.points[i];
  3255. }
  3256. }
  3257. ), false);
  3258. point.label = label.options;
  3259. }, this);
  3260. }
  3261. },
  3262. /**
  3263. * An elliott wave annotation.
  3264. *
  3265. * @extends annotations.crookedLine
  3266. * @sample highcharts/annotations-advanced/elliott-wave/
  3267. * Elliott wave
  3268. * @product highstock
  3269. * @optionparent annotations.elliottWave
  3270. */
  3271. {
  3272. typeOptions: {
  3273. /**
  3274. * @type {Object}
  3275. * @extends annotations.crookedLine.labelOptions
  3276. * @apioption annotations.crookedLine.typeOptions.points.label
  3277. */
  3278. /**
  3279. * @ignore
  3280. */
  3281. labels: ['(0)', '(A)', '(B)', '(C)', '(D)', '(E)'],
  3282. line: {
  3283. strokeWidth: 1
  3284. }
  3285. },
  3286. labelOptions: {
  3287. align: 'center',
  3288. allowOverlap: true,
  3289. crop: true,
  3290. overflow: 'none',
  3291. type: 'rect',
  3292. backgroundColor: 'none',
  3293. borderWidth: 0,
  3294. y: -5
  3295. }
  3296. });
  3297. Annotation.types.elliottWave = ElliottWave;
  3298. return ElliottWave;
  3299. }(Highcharts));
  3300. var Tunnel = (function (H) {
  3301. var Annotation = H.Annotation,
  3302. CrookedLine = Annotation.types.crookedLine,
  3303. ControlPoint = Annotation.ControlPoint,
  3304. MockPoint = Annotation.MockPoint;
  3305. function getSecondCoordinate(p1, p2, x) {
  3306. return (p2.y - p1.y) / (p2.x - p1.x) * (x - p1.x) + p1.y;
  3307. }
  3308. /**
  3309. * @class
  3310. * @extends Annotation.CrookedLine
  3311. * @memberOf Annotation
  3312. **/
  3313. function Tunnel() {
  3314. CrookedLine.apply(this, arguments);
  3315. }
  3316. H.extendAnnotation(
  3317. Tunnel,
  3318. CrookedLine,
  3319. /** @lends Annotation.Tunnel# */
  3320. {
  3321. getPointsOptions: function () {
  3322. var pointsOptions =
  3323. CrookedLine.prototype.getPointsOptions.call(this);
  3324. pointsOptions[2] = this.heightPointOptions(pointsOptions[1]);
  3325. pointsOptions[3] = this.heightPointOptions(pointsOptions[0]);
  3326. return pointsOptions;
  3327. },
  3328. getControlPointsOptions: function () {
  3329. return this.getPointsOptions().slice(0, 2);
  3330. },
  3331. heightPointOptions: function (pointOptions) {
  3332. var heightPointOptions = H.merge(pointOptions);
  3333. heightPointOptions.y += this.options.typeOptions.height;
  3334. return heightPointOptions;
  3335. },
  3336. addControlPoints: function () {
  3337. CrookedLine.prototype.addControlPoints.call(this);
  3338. var options = this.options,
  3339. controlPoint = new ControlPoint(
  3340. this.chart,
  3341. this,
  3342. H.merge(
  3343. options.controlPointOptions,
  3344. options.typeOptions.heightControlPoint
  3345. ),
  3346. 2
  3347. );
  3348. this.controlPoints.push(controlPoint);
  3349. options.typeOptions.heightControlPoint = controlPoint.options;
  3350. },
  3351. addShapes: function () {
  3352. this.addLine();
  3353. this.addBackground();
  3354. },
  3355. addLine: function () {
  3356. var line = this.initShape(
  3357. H.merge(this.options.typeOptions.line, {
  3358. type: 'path',
  3359. points: [
  3360. this.points[0],
  3361. this.points[1],
  3362. function (target) {
  3363. var pointOptions = MockPoint.pointToOptions(
  3364. target.annotation.points[2]
  3365. );
  3366. pointOptions.command = 'M';
  3367. return pointOptions;
  3368. },
  3369. this.points[3]
  3370. ]
  3371. }),
  3372. false
  3373. );
  3374. this.options.typeOptions.line = line.options;
  3375. },
  3376. addBackground: function () {
  3377. var background = this.initShape(H.merge(
  3378. this.options.typeOptions.background,
  3379. {
  3380. type: 'path',
  3381. points: this.points.slice()
  3382. }
  3383. ));
  3384. this.options.typeOptions.background = background.options;
  3385. },
  3386. /**
  3387. * Translate start or end ("left" or "right") side of the tunnel.
  3388. *
  3389. * @param {number} dx - the amount of x translation
  3390. * @param {number} dy - the amount of y translation
  3391. * @param {boolean} [end] - whether to translate start or end side
  3392. */
  3393. translateSide: function (dx, dy, end) {
  3394. var topIndex = Number(end),
  3395. bottomIndex = topIndex === 0 ? 3 : 2;
  3396. this.translatePoint(dx, dy, topIndex);
  3397. this.translatePoint(dx, dy, bottomIndex);
  3398. },
  3399. /**
  3400. * Translate height of the tunnel.
  3401. *
  3402. * @param {number} dh - the amount of height translation
  3403. */
  3404. translateHeight: function (dh) {
  3405. this.translatePoint(0, dh, 2);
  3406. this.translatePoint(0, dh, 3);
  3407. this.options.typeOptions.height =
  3408. this.points[3].y - this.points[0].y;
  3409. }
  3410. },
  3411. /**
  3412. * A tunnel annotation.
  3413. *
  3414. * @extends annotations.crookedLine
  3415. * @sample highcharts/annotations-advanced/tunnel/
  3416. * Tunnel
  3417. * @product highstock
  3418. * @optionparent annotations.tunnel
  3419. */
  3420. {
  3421. typeOptions: {
  3422. xAxis: 0,
  3423. yAxis: 0,
  3424. /**
  3425. * Background options.
  3426. *
  3427. * @type {Object}
  3428. * @excluding height, point, points, r, type, width, markerEnd,
  3429. * markerStart
  3430. */
  3431. background: {
  3432. fill: 'rgba(130, 170, 255, 0.4)',
  3433. strokeWidth: 0
  3434. },
  3435. line: {
  3436. strokeWidth: 1
  3437. },
  3438. /**
  3439. * The height of the annotation in terms of yAxis.
  3440. */
  3441. height: -2,
  3442. /**
  3443. * Options for the control point which controls
  3444. * the annotation's height.
  3445. *
  3446. * @extends annotations.crookedLine.controlPointOptions
  3447. * @excluding positioner, events
  3448. */
  3449. heightControlPoint: {
  3450. positioner: function (target) {
  3451. var startXY = MockPoint.pointToPixels(target.points[2]),
  3452. endXY = MockPoint.pointToPixels(target.points[3]),
  3453. x = (startXY.x + endXY.x) / 2;
  3454. return {
  3455. x: x - this.graphic.width / 2,
  3456. y: getSecondCoordinate(startXY, endXY, x) -
  3457. this.graphic.height / 2
  3458. };
  3459. },
  3460. events: {
  3461. drag: function (e, target) {
  3462. if (
  3463. target.chart.isInsidePlot(
  3464. e.chartX - target.chart.plotLeft,
  3465. e.chartY - target.chart.plotTop
  3466. )
  3467. ) {
  3468. target.translateHeight(
  3469. this.mouseMoveToTranslation(e).y
  3470. );
  3471. target.redraw(false);
  3472. }
  3473. }
  3474. }
  3475. }
  3476. },
  3477. /**
  3478. * @extends annotations.crookedLine.controlPointOptions
  3479. * @excluding positioner, events
  3480. */
  3481. controlPointOptions: {
  3482. events: {
  3483. drag: function (e, target) {
  3484. if (
  3485. target.chart.isInsidePlot(
  3486. e.chartX - target.chart.plotLeft,
  3487. e.chartY - target.chart.plotTop
  3488. )
  3489. ) {
  3490. var translation = this.mouseMoveToTranslation(e);
  3491. target.translateSide(
  3492. translation.x,
  3493. translation.y,
  3494. this.index
  3495. );
  3496. target.redraw(false);
  3497. }
  3498. }
  3499. }
  3500. }
  3501. }
  3502. );
  3503. Annotation.types.tunnel = Tunnel;
  3504. return Tunnel;
  3505. }(Highcharts));
  3506. var InfinityLine = (function (H) {
  3507. var Annotation = H.Annotation,
  3508. MockPoint = Annotation.MockPoint,
  3509. CrookedLine = Annotation.types.crookedLine;
  3510. /**
  3511. * @class
  3512. * @extends Annotation.CrookedLine
  3513. * @memberOf Annotation
  3514. */
  3515. function InfinityLine() {
  3516. CrookedLine.apply(this, arguments);
  3517. }
  3518. InfinityLine.findEdgeCoordinate = function (
  3519. firstPoint,
  3520. secondPoint,
  3521. xOrY,
  3522. edgePointFirstCoordinate
  3523. ) {
  3524. var xOrYOpposite = xOrY === 'x' ? 'y' : 'x';
  3525. // solves equation for x or y
  3526. // y - y1 = (y2 - y1) / (x2 - x1) * (x - x1)
  3527. return (
  3528. (secondPoint[xOrY] - firstPoint[xOrY]) *
  3529. (edgePointFirstCoordinate - firstPoint[xOrYOpposite]) /
  3530. (secondPoint[xOrYOpposite] - firstPoint[xOrYOpposite]) +
  3531. firstPoint[xOrY]
  3532. );
  3533. };
  3534. InfinityLine.findEdgePoint = function (firstPoint, secondPoint) {
  3535. var xAxis = firstPoint.series.xAxis,
  3536. yAxis = secondPoint.series.yAxis,
  3537. firstPointPixels = MockPoint.pointToPixels(firstPoint),
  3538. secondPointPixels = MockPoint.pointToPixels(secondPoint),
  3539. deltaX = secondPointPixels.x - firstPointPixels.x,
  3540. deltaY = secondPointPixels.y - firstPointPixels.y,
  3541. xAxisMin = xAxis.left,
  3542. xAxisMax = xAxisMin + xAxis.width,
  3543. yAxisMin = yAxis.top,
  3544. yAxisMax = yAxisMin + yAxis.height,
  3545. xLimit = deltaX < 0 ? xAxisMin : xAxisMax,
  3546. yLimit = deltaY < 0 ? yAxisMin : yAxisMax,
  3547. edgePoint = {
  3548. x: deltaX === 0 ? firstPointPixels.x : xLimit,
  3549. y: deltaY === 0 ? firstPointPixels.y : yLimit
  3550. },
  3551. edgePointX,
  3552. edgePointY,
  3553. swap;
  3554. if (deltaX !== 0 && deltaY !== 0) {
  3555. edgePointY = InfinityLine.findEdgeCoordinate(
  3556. firstPointPixels,
  3557. secondPointPixels,
  3558. 'y',
  3559. xLimit
  3560. );
  3561. edgePointX = InfinityLine.findEdgeCoordinate(
  3562. firstPointPixels,
  3563. secondPointPixels,
  3564. 'x',
  3565. yLimit
  3566. );
  3567. if (edgePointY >= yAxisMin && edgePointY <= yAxisMax) {
  3568. edgePoint.x = xLimit;
  3569. edgePoint.y = edgePointY;
  3570. } else {
  3571. edgePoint.x = edgePointX;
  3572. edgePoint.y = yLimit;
  3573. }
  3574. }
  3575. edgePoint.x -= xAxisMin;
  3576. edgePoint.y -= yAxisMin;
  3577. if (firstPoint.series.chart.inverted) {
  3578. swap = edgePoint.x;
  3579. edgePoint.x = edgePoint.y;
  3580. edgePoint.y = swap;
  3581. }
  3582. return edgePoint;
  3583. };
  3584. var edgePoint = function (startIndex, endIndex) {
  3585. return function (target) {
  3586. var annotation = target.annotation,
  3587. points = annotation.points,
  3588. type = annotation.options.typeOptions.type;
  3589. if (type === 'horizontalLine') {
  3590. // Horizontal line has only one point,
  3591. // make a copy of it:
  3592. points = [
  3593. points[0],
  3594. new MockPoint(
  3595. annotation.chart,
  3596. points[0].target,
  3597. {
  3598. x: points[0].x + 1,
  3599. y: points[0].y,
  3600. xAxis: points[0].options.xAxis,
  3601. yAxis: points[0].options.yAxis
  3602. }
  3603. )
  3604. ];
  3605. } else if (type === 'verticalLine') {
  3606. // The same for verticalLine type:
  3607. points = [
  3608. points[0],
  3609. new MockPoint(
  3610. annotation.chart,
  3611. points[0].target,
  3612. {
  3613. x: points[0].x,
  3614. y: points[0].y + 1,
  3615. xAxis: points[0].options.xAxis,
  3616. yAxis: points[0].options.yAxis
  3617. }
  3618. )
  3619. ];
  3620. }
  3621. return InfinityLine.findEdgePoint(
  3622. points[startIndex],
  3623. points[endIndex]
  3624. );
  3625. };
  3626. };
  3627. InfinityLine.endEdgePoint = edgePoint(0, 1);
  3628. InfinityLine.startEdgePoint = edgePoint(1, 0);
  3629. H.extendAnnotation(
  3630. InfinityLine,
  3631. CrookedLine,
  3632. /** @lends Annotation.InfinityLine# */{
  3633. addShapes: function () {
  3634. var typeOptions = this.options.typeOptions,
  3635. points = [
  3636. this.points[0],
  3637. InfinityLine.endEdgePoint
  3638. ];
  3639. if (typeOptions.type.match(/Line/g)) {
  3640. points[0] = InfinityLine.startEdgePoint;
  3641. }
  3642. var line = this.initShape(
  3643. H.merge(typeOptions.line, {
  3644. type: 'path',
  3645. points: points
  3646. }),
  3647. false
  3648. );
  3649. typeOptions.line = line.options;
  3650. }
  3651. }
  3652. );
  3653. /**
  3654. * An infinity line annotation.
  3655. *
  3656. * @extends annotations.crookedLine
  3657. * @sample highcharts/annotations-advanced/infinity-line/
  3658. * Infinity Line
  3659. *
  3660. * @product highstock
  3661. *
  3662. * @apioption annotations.infinityLine
  3663. */
  3664. Annotation.types.infinityLine = InfinityLine;
  3665. return InfinityLine;
  3666. }(Highcharts));
  3667. var Fibonacci = (function (H) {
  3668. var Annotation = H.Annotation,
  3669. MockPoint = Annotation.MockPoint,
  3670. Tunnel = Annotation.types.tunnel;
  3671. var createPathDGenerator = function (retracementIndex, isBackground) {
  3672. return function () {
  3673. var annotation = this.annotation,
  3674. leftTop = this.anchor(
  3675. annotation.startRetracements[retracementIndex]
  3676. ).absolutePosition,
  3677. rightTop = this.anchor(
  3678. annotation.endRetracements[retracementIndex]
  3679. ).absolutePosition,
  3680. d = [
  3681. 'M',
  3682. Math.round(leftTop.x),
  3683. Math.round(leftTop.y),
  3684. 'L',
  3685. Math.round(rightTop.x),
  3686. Math.round(rightTop.y)
  3687. ],
  3688. rightBottom,
  3689. leftBottom;
  3690. if (isBackground) {
  3691. rightBottom = this.anchor(
  3692. annotation.endRetracements[retracementIndex - 1]
  3693. ).absolutePosition;
  3694. leftBottom = this.anchor(
  3695. annotation.startRetracements[retracementIndex - 1]
  3696. ).absolutePosition;
  3697. d.push(
  3698. 'L',
  3699. Math.round(rightBottom.x),
  3700. Math.round(rightBottom.y),
  3701. 'L',
  3702. Math.round(leftBottom.x),
  3703. Math.round(leftBottom.y)
  3704. );
  3705. }
  3706. return d;
  3707. };
  3708. };
  3709. /**
  3710. * @class
  3711. * @extends Annotation.Tunnel
  3712. * @memberOf Annotation
  3713. **/
  3714. function Fibonacci() {
  3715. this.startRetracements = [];
  3716. this.endRetracements = [];
  3717. Tunnel.apply(this, arguments);
  3718. }
  3719. Fibonacci.levels = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
  3720. H.extendAnnotation(Fibonacci, Tunnel,
  3721. /** @lends Annotation.Fibonacci# */
  3722. {
  3723. linkPoints: function () {
  3724. Tunnel.prototype.linkPoints.call(this);
  3725. this.linkRetracementsPoints();
  3726. },
  3727. linkRetracementsPoints: function () {
  3728. var points = this.points,
  3729. startDiff = points[0].y - points[3].y,
  3730. endDiff = points[1].y - points[2].y,
  3731. startX = points[0].x,
  3732. endX = points[1].x;
  3733. Fibonacci.levels.forEach(function (level, i) {
  3734. var startRetracement = points[0].y - startDiff * level,
  3735. endRetracement = points[1].y - endDiff * level;
  3736. this.linkRetracementPoint(
  3737. i,
  3738. startX,
  3739. startRetracement,
  3740. this.startRetracements
  3741. );
  3742. this.linkRetracementPoint(
  3743. i,
  3744. endX,
  3745. endRetracement,
  3746. this.endRetracements
  3747. );
  3748. }, this);
  3749. },
  3750. linkRetracementPoint: function (
  3751. pointIndex,
  3752. x,
  3753. y,
  3754. retracements
  3755. ) {
  3756. var point = retracements[pointIndex],
  3757. typeOptions = this.options.typeOptions;
  3758. if (!point) {
  3759. retracements[pointIndex] = new MockPoint(
  3760. this.chart,
  3761. this,
  3762. {
  3763. x: x,
  3764. y: y,
  3765. xAxis: typeOptions.xAxis,
  3766. yAxis: typeOptions.yAxis
  3767. }
  3768. );
  3769. } else {
  3770. point.options.x = x;
  3771. point.options.y = y;
  3772. point.refresh();
  3773. }
  3774. },
  3775. addShapes: function () {
  3776. Fibonacci.levels.forEach(function (level, i) {
  3777. this.initShape({
  3778. type: 'path',
  3779. d: createPathDGenerator(i)
  3780. }, false);
  3781. if (i > 0) {
  3782. this.initShape({
  3783. type: 'path',
  3784. fill: this.options.typeOptions.backgroundColors[i - 1],
  3785. strokeWidth: 0,
  3786. d: createPathDGenerator(i, true)
  3787. });
  3788. }
  3789. }, this);
  3790. },
  3791. addLabels: function () {
  3792. Fibonacci.levels.forEach(function (level, i) {
  3793. var options = this.options.typeOptions,
  3794. label = this.initLabel(
  3795. H.merge(options.labels[i], {
  3796. point: function (target) {
  3797. var point = MockPoint.pointToOptions(
  3798. target.annotation.startRetracements[i]
  3799. );
  3800. return point;
  3801. },
  3802. text: level.toString()
  3803. })
  3804. );
  3805. options.labels[i] = label.options;
  3806. }, this);
  3807. }
  3808. },
  3809. /**
  3810. * A fibonacci annotation.
  3811. *
  3812. * @extends annotations.crookedLine
  3813. * @sample highcharts/annotations-advanced/fibonacci/
  3814. * Fibonacci
  3815. *
  3816. * @product highstock
  3817. * @optionparent annotations.fibonacci
  3818. */
  3819. {
  3820. typeOptions: {
  3821. /**
  3822. * The height of the fibonacci in terms of yAxis.
  3823. */
  3824. height: 2,
  3825. /**
  3826. * An array of background colors:
  3827. * Default to:
  3828. * <pre>
  3829. [
  3830. 'rgba(130, 170, 255, 0.4)',
  3831. 'rgba(139, 191, 216, 0.4)',
  3832. 'rgba(150, 216, 192, 0.4)',
  3833. 'rgba(156, 229, 161, 0.4)',
  3834. 'rgba(162, 241, 130, 0.4)',
  3835. 'rgba(169, 255, 101, 0.4)'
  3836. ]
  3837. </pre>
  3838. */
  3839. backgroundColors: [
  3840. 'rgba(130, 170, 255, 0.4)',
  3841. 'rgba(139, 191, 216, 0.4)',
  3842. 'rgba(150, 216, 192, 0.4)',
  3843. 'rgba(156, 229, 161, 0.4)',
  3844. 'rgba(162, 241, 130, 0.4)',
  3845. 'rgba(169, 255, 101, 0.4)'
  3846. ],
  3847. /**
  3848. * The color of line.
  3849. */
  3850. lineColor: 'grey',
  3851. /**
  3852. * An array of colors for the lines.
  3853. */
  3854. lineColors: [],
  3855. /**
  3856. * An array with options for the labels.
  3857. *
  3858. * @type {Array<Object>}
  3859. * @extends annotations.crookedLine.labelOptions
  3860. * @apioption annotations.fibonacci.typeOptions.labels
  3861. */
  3862. labels: []
  3863. },
  3864. labelOptions: {
  3865. allowOverlap: true,
  3866. align: 'right',
  3867. backgroundColor: 'none',
  3868. borderWidth: 0,
  3869. crop: false,
  3870. overflow: 'none',
  3871. shape: 'rect',
  3872. style: {
  3873. color: 'grey'
  3874. },
  3875. verticalAlign: 'middle',
  3876. y: 0
  3877. }
  3878. });
  3879. Annotation.types.fibonacci = Fibonacci;
  3880. return Fibonacci;
  3881. }(Highcharts));
  3882. var Pitchfork = (function (H) {
  3883. var Annotation = H.Annotation,
  3884. MockPoint = Annotation.MockPoint,
  3885. InfinityLine = Annotation.types.infinityLine;
  3886. /**
  3887. * @class
  3888. * @extends Highcharts.InfinityLine
  3889. * @memberOf Highcharts
  3890. **/
  3891. function Pitchfork() {
  3892. InfinityLine.apply(this, arguments);
  3893. }
  3894. Pitchfork.findEdgePoint = function (
  3895. point,
  3896. firstAnglePoint,
  3897. secondAnglePoint
  3898. ) {
  3899. var angle = Math.atan2(
  3900. secondAnglePoint.plotY - firstAnglePoint.plotY,
  3901. secondAnglePoint.plotX - firstAnglePoint.plotX
  3902. ),
  3903. distance = 1e7;
  3904. return {
  3905. x: point.plotX + distance * Math.cos(angle),
  3906. y: point.plotY + distance * Math.sin(angle)
  3907. };
  3908. };
  3909. Pitchfork.middleLineEdgePoint = function (target) {
  3910. var annotation = target.annotation,
  3911. points = annotation.points;
  3912. return InfinityLine.findEdgePoint(
  3913. points[0],
  3914. new MockPoint(
  3915. annotation.chart,
  3916. target,
  3917. annotation.midPointOptions()
  3918. )
  3919. );
  3920. };
  3921. var outerLineEdgePoint = function (firstPointIndex) {
  3922. return function (target) {
  3923. var annotation = target.annotation,
  3924. points = annotation.points;
  3925. return Pitchfork.findEdgePoint(
  3926. points[firstPointIndex],
  3927. points[0],
  3928. new MockPoint(
  3929. annotation.chart,
  3930. target,
  3931. annotation.midPointOptions()
  3932. )
  3933. );
  3934. };
  3935. };
  3936. Pitchfork.topLineEdgePoint = outerLineEdgePoint(1);
  3937. Pitchfork.bottomLineEdgePoint = outerLineEdgePoint(0);
  3938. H.extendAnnotation(Pitchfork, InfinityLine,
  3939. {
  3940. midPointOptions: function () {
  3941. var points = this.points;
  3942. return {
  3943. x: (points[1].x + points[2].x) / 2,
  3944. y: (points[1].y + points[2].y) / 2,
  3945. xAxis: points[0].series.xAxis,
  3946. yAxis: points[0].series.yAxis
  3947. };
  3948. },
  3949. addShapes: function () {
  3950. this.addLines();
  3951. this.addBackgrounds();
  3952. },
  3953. addLines: function () {
  3954. this.initShape({
  3955. type: 'path',
  3956. points: [
  3957. this.points[0],
  3958. Pitchfork.middleLineEdgePoint
  3959. ]
  3960. }, false);
  3961. this.initShape({
  3962. type: 'path',
  3963. points: [
  3964. this.points[1],
  3965. Pitchfork.topLineEdgePoint
  3966. ]
  3967. }, false);
  3968. this.initShape({
  3969. type: 'path',
  3970. points: [
  3971. this.points[2],
  3972. Pitchfork.bottomLineEdgePoint
  3973. ]
  3974. }, false);
  3975. },
  3976. addBackgrounds: function () {
  3977. var shapes = this.shapes,
  3978. typeOptions = this.options.typeOptions;
  3979. var innerBackground = this.initShape(
  3980. H.merge(typeOptions.innerBackground, {
  3981. type: 'path',
  3982. points: [
  3983. function (target) {
  3984. var annotation = target.annotation,
  3985. points = annotation.points,
  3986. midPointOptions = annotation.midPointOptions();
  3987. return {
  3988. x: (points[1].x + midPointOptions.x) / 2,
  3989. y: (points[1].y + midPointOptions.y) / 2,
  3990. xAxis: midPointOptions.xAxis,
  3991. yAxis: midPointOptions.yAxis
  3992. };
  3993. },
  3994. shapes[1].points[1],
  3995. shapes[2].points[1],
  3996. function (target) {
  3997. var annotation = target.annotation,
  3998. points = annotation.points,
  3999. midPointOptions = annotation.midPointOptions();
  4000. return {
  4001. x: (midPointOptions.x + points[2].x) / 2,
  4002. y: (midPointOptions.y + points[2].y) / 2,
  4003. xAxis: midPointOptions.xAxis,
  4004. yAxis: midPointOptions.yAxis
  4005. };
  4006. }
  4007. ]
  4008. })
  4009. );
  4010. var outerBackground = this.initShape(
  4011. H.merge(typeOptions.outerBackground, {
  4012. type: 'path',
  4013. points: [
  4014. this.points[1],
  4015. shapes[1].points[1],
  4016. shapes[2].points[1],
  4017. this.points[2]
  4018. ]
  4019. })
  4020. );
  4021. typeOptions.innerBackground = innerBackground.options;
  4022. typeOptions.outerBackground = outerBackground.options;
  4023. }
  4024. },
  4025. /**
  4026. * A pitchfork annotation.
  4027. *
  4028. * @extends annotations.infinityLine
  4029. * @sample highcharts/annotations-advanced/pitchfork/
  4030. * Pitchfork
  4031. * @product highstock
  4032. * @optionparent annotations.pitchfork
  4033. */
  4034. {
  4035. typeOptions: {
  4036. /**
  4037. * Inner background options.
  4038. *
  4039. * @extends annotations.crookedLine.shapeOptions
  4040. * @excluding height, r, type, width
  4041. */
  4042. innerBackground: {
  4043. fill: 'rgba(130, 170, 255, 0.4)',
  4044. strokeWidth: 0
  4045. },
  4046. /**
  4047. * Outer background options.
  4048. *
  4049. * @extends annotations.crookedLine.shapeOptions
  4050. * @excluding height, r, type, width
  4051. */
  4052. outerBackground: {
  4053. fill: 'rgba(156, 229, 161, 0.4)',
  4054. strokeWidth: 0
  4055. }
  4056. }
  4057. });
  4058. Annotation.types.pitchfork = Pitchfork;
  4059. return Pitchfork;
  4060. }(Highcharts));
  4061. var VerticalLine = (function (H) {
  4062. var Annotation = H.Annotation,
  4063. MockPoint = Annotation.MockPoint;
  4064. /**
  4065. * @class
  4066. * @extends Annotation
  4067. * @memberOf Highcharts
  4068. **/
  4069. function VerticalLine() {
  4070. H.Annotation.apply(this, arguments);
  4071. }
  4072. VerticalLine.connectorFirstPoint = function (target) {
  4073. var annotation = target.annotation,
  4074. point = annotation.points[0],
  4075. xy = MockPoint.pointToPixels(point, true),
  4076. y = xy.y,
  4077. offset = annotation.options.typeOptions.label.offset;
  4078. if (annotation.chart.inverted) {
  4079. y = xy.x;
  4080. }
  4081. return {
  4082. x: point.x,
  4083. xAxis: point.series.xAxis,
  4084. y: y + offset
  4085. };
  4086. };
  4087. VerticalLine.connectorSecondPoint = function (target) {
  4088. var annotation = target.annotation,
  4089. typeOptions = annotation.options.typeOptions,
  4090. point = annotation.points[0],
  4091. yOffset = typeOptions.yOffset,
  4092. xy = MockPoint.pointToPixels(point, true),
  4093. y = xy[annotation.chart.inverted ? 'x' : 'y'];
  4094. if (typeOptions.label.offset < 0) {
  4095. yOffset *= -1;
  4096. }
  4097. return {
  4098. x: point.x,
  4099. xAxis: point.series.xAxis,
  4100. y: y + yOffset
  4101. };
  4102. };
  4103. H.extendAnnotation(VerticalLine, null,
  4104. /** @lends Annotation.VerticalLine# */
  4105. {
  4106. getPointsOptions: function () {
  4107. return [this.options.typeOptions.point];
  4108. },
  4109. addShapes: function () {
  4110. var typeOptions = this.options.typeOptions,
  4111. connector = this.initShape(
  4112. H.merge(typeOptions.connector, {
  4113. type: 'path',
  4114. points: [
  4115. VerticalLine.connectorFirstPoint,
  4116. VerticalLine.connectorSecondPoint
  4117. ]
  4118. }),
  4119. false
  4120. );
  4121. typeOptions.connector = connector.options;
  4122. },
  4123. addLabels: function () {
  4124. var typeOptions = this.options.typeOptions,
  4125. labelOptions = typeOptions.label,
  4126. x = 0,
  4127. y = labelOptions.offset,
  4128. verticalAlign = labelOptions.offset < 0 ? 'bottom' : 'top',
  4129. align = 'center';
  4130. if (this.chart.inverted) {
  4131. x = labelOptions.offset;
  4132. y = 0;
  4133. verticalAlign = 'middle';
  4134. align = labelOptions.offset < 0 ? 'right' : 'left';
  4135. }
  4136. var label = this.initLabel(
  4137. H.merge(labelOptions, {
  4138. verticalAlign: verticalAlign,
  4139. align: align,
  4140. x: x,
  4141. y: y
  4142. })
  4143. );
  4144. typeOptions.label = label.options;
  4145. }
  4146. },
  4147. /**
  4148. * A vertical line annotation.
  4149. *
  4150. * @extends annotations.crookedLine
  4151. * @excluding labels, shapes, controlPointOptions
  4152. * @sample highcharts/annotations-advanced/vertical-line/
  4153. * Vertical line
  4154. * @product highstock
  4155. * @optionparent annotations.verticalLine
  4156. */
  4157. {
  4158. typeOptions: {
  4159. /**
  4160. * @ignore
  4161. */
  4162. yOffset: 10,
  4163. /**
  4164. * Label options.
  4165. *
  4166. * @extends annotations.crookedLine.labelOptions
  4167. */
  4168. label: {
  4169. offset: -40,
  4170. point: function (target) {
  4171. return target.annotation.points[0];
  4172. },
  4173. allowOverlap: true,
  4174. backgroundColor: 'none',
  4175. borderWidth: 0,
  4176. crop: true,
  4177. overflow: 'none',
  4178. shape: 'rect',
  4179. text: '{y:.2f}'
  4180. },
  4181. /**
  4182. * Connector options.
  4183. *
  4184. * @extends annotations.crookedLine.shapeOptions
  4185. * @excluding height, r, type, width
  4186. */
  4187. connector: {
  4188. strokeWidth: 1,
  4189. markerEnd: 'arrow'
  4190. }
  4191. }
  4192. });
  4193. Annotation.types.verticalLine = VerticalLine;
  4194. return VerticalLine;
  4195. }(Highcharts));
  4196. var Measure = (function (H) {
  4197. var Annotation = H.Annotation,
  4198. ControlPoint = Annotation.ControlPoint,
  4199. merge = H.merge,
  4200. isNumber = H.isNumber;
  4201. /**
  4202. * @class
  4203. * @extends Annotation
  4204. * @memberOf Annotation
  4205. */
  4206. function Measure() {
  4207. Annotation.apply(this, arguments);
  4208. }
  4209. Annotation.types.measure = Measure;
  4210. H.extendAnnotation(Measure, null,
  4211. /** @lends Annotation.Measure# */
  4212. {
  4213. /**
  4214. *
  4215. * Init annotation object.
  4216. *
  4217. */
  4218. init: function () {
  4219. Annotation.prototype.init.apply(this, arguments);
  4220. this.offsetX = 0;
  4221. this.offsetY = 0;
  4222. this.resizeX = 0;
  4223. this.resizeY = 0;
  4224. this.calculations.init.call(this);
  4225. this.addValues();
  4226. this.addShapes();
  4227. },
  4228. /**
  4229. * Overrides default setter to get axes from typeOptions.
  4230. */
  4231. setClipAxes: function () {
  4232. this.clipXAxis = this.chart.xAxis[this.options.typeOptions.xAxis];
  4233. this.clipYAxis = this.chart.yAxis[this.options.typeOptions.yAxis];
  4234. },
  4235. /**
  4236. * Get measure points configuration objects.
  4237. *
  4238. * @return {Array<Highcharts.MockPointOptions>}
  4239. */
  4240. pointsOptions: function () {
  4241. return this.options.options.points;
  4242. },
  4243. /**
  4244. * Get points configuration objects for shapes.
  4245. *
  4246. * @return {Array<Highcharts.MockPointOptions>}
  4247. */
  4248. shapePointsOptions: function () {
  4249. var options = this.options.typeOptions,
  4250. xAxis = options.xAxis,
  4251. yAxis = options.yAxis;
  4252. return [
  4253. {
  4254. x: this.xAxisMin,
  4255. y: this.yAxisMin,
  4256. xAxis: xAxis,
  4257. yAxis: yAxis
  4258. },
  4259. {
  4260. x: this.xAxisMax,
  4261. y: this.yAxisMin,
  4262. xAxis: xAxis,
  4263. yAxis: yAxis
  4264. },
  4265. {
  4266. x: this.xAxisMax,
  4267. y: this.yAxisMax,
  4268. xAxis: xAxis,
  4269. yAxis: yAxis
  4270. },
  4271. {
  4272. x: this.xAxisMin,
  4273. y: this.yAxisMax,
  4274. xAxis: xAxis,
  4275. yAxis: yAxis
  4276. }
  4277. ];
  4278. },
  4279. addControlPoints: function () {
  4280. var selectType = this.options.typeOptions.selectType,
  4281. controlPoint;
  4282. controlPoint = new ControlPoint(
  4283. this.chart,
  4284. this,
  4285. this.options.controlPointOptions,
  4286. 0
  4287. );
  4288. this.controlPoints.push(controlPoint);
  4289. // add extra controlPoint for horizontal and vertical range
  4290. if (selectType !== 'xy') {
  4291. controlPoint = new ControlPoint(
  4292. this.chart,
  4293. this,
  4294. this.options.controlPointOptions,
  4295. 1
  4296. );
  4297. this.controlPoints.push(controlPoint);
  4298. }
  4299. },
  4300. /**
  4301. * Add label with calculated values (min, max, average, bins).
  4302. *
  4303. * @param {Boolean} resize - the flag for resize shape
  4304. *
  4305. */
  4306. addValues: function (resize) {
  4307. var options = this.options.typeOptions,
  4308. formatter = options.label.formatter,
  4309. typeOptions = this.options.typeOptions,
  4310. chart = this.chart,
  4311. inverted = chart.options.chart.inverted,
  4312. xAxis = chart.xAxis[typeOptions.xAxis],
  4313. yAxis = chart.yAxis[typeOptions.yAxis];
  4314. // set xAxisMin, xAxisMax, yAxisMin, yAxisMax
  4315. this.calculations.recalculate.call(this, resize);
  4316. if (!options.label.enabled) {
  4317. return;
  4318. }
  4319. if (this.labels.length > 0) {
  4320. this.labels[0].text = (formatter && formatter.call(this)) ||
  4321. this.calculations.defaultFormatter.call(this);
  4322. } else {
  4323. this.initLabel(H.extend({
  4324. shape: 'rect',
  4325. backgroundColor: 'none',
  4326. color: 'black',
  4327. borderWidth: 0,
  4328. dashStyle: 'dash',
  4329. overflow: 'none',
  4330. align: 'left',
  4331. vertical: 'top',
  4332. crop: true,
  4333. point: function (target) {
  4334. var annotation = target.annotation,
  4335. top = chart.plotTop,
  4336. left = chart.plotLeft;
  4337. return {
  4338. x: (inverted ? top : 10) +
  4339. xAxis.toPixels(annotation.xAxisMin, !inverted),
  4340. y: (inverted ? -left + 10 : top) +
  4341. yAxis.toPixels(annotation.yAxisMin)
  4342. };
  4343. },
  4344. text: (formatter && formatter.call(this)) ||
  4345. this.calculations.defaultFormatter.call(this)
  4346. }, options.label));
  4347. }
  4348. },
  4349. /**
  4350. * add shapes - crosshair, background (rect)
  4351. *
  4352. */
  4353. addShapes: function () {
  4354. this.addCrosshairs();
  4355. this.addBackground();
  4356. },
  4357. /**
  4358. * Add background shape.
  4359. */
  4360. addBackground: function () {
  4361. var shapePoints = this.shapePointsOptions();
  4362. if (shapePoints[0].x === undefined) {
  4363. return;
  4364. }
  4365. this.initShape(H.extend({
  4366. type: 'path',
  4367. points: this.shapePointsOptions()
  4368. }, this.options.typeOptions.background), false);
  4369. },
  4370. /**
  4371. * Add internal crosshair shapes (on top and bottom)
  4372. */
  4373. addCrosshairs: function () {
  4374. var chart = this.chart,
  4375. options = this.options.typeOptions,
  4376. point = this.options.typeOptions.point,
  4377. xAxis = chart.xAxis[options.xAxis],
  4378. yAxis = chart.yAxis[options.yAxis],
  4379. inverted = chart.options.chart.inverted,
  4380. xAxisMin = xAxis.toPixels(this.xAxisMin),
  4381. xAxisMax = xAxis.toPixels(this.xAxisMax),
  4382. yAxisMin = yAxis.toPixels(this.yAxisMin),
  4383. yAxisMax = yAxis.toPixels(this.yAxisMax),
  4384. defaultOptions = {
  4385. point: point,
  4386. type: 'path'
  4387. },
  4388. pathH = [],
  4389. pathV = [],
  4390. crosshairOptionsX, crosshairOptionsY;
  4391. if (inverted) {
  4392. xAxisMin = yAxis.toPixels(this.yAxisMin);
  4393. xAxisMax = yAxis.toPixels(this.yAxisMax);
  4394. yAxisMin = xAxis.toPixels(this.xAxisMin);
  4395. yAxisMax = xAxis.toPixels(this.xAxisMax);
  4396. }
  4397. // horizontal line
  4398. if (options.crosshairX.enabled) {
  4399. pathH = [
  4400. 'M',
  4401. xAxisMin,
  4402. yAxisMin + ((yAxisMax - yAxisMin) / 2),
  4403. 'L',
  4404. xAxisMax,
  4405. yAxisMin + ((yAxisMax - yAxisMin) / 2)
  4406. ];
  4407. }
  4408. // vertical line
  4409. if (options.crosshairY.enabled) {
  4410. pathV = [
  4411. 'M',
  4412. xAxisMin + ((xAxisMax - xAxisMin) / 2),
  4413. yAxisMin,
  4414. 'L',
  4415. xAxisMin + ((xAxisMax - xAxisMin) / 2),
  4416. yAxisMax
  4417. ];
  4418. }
  4419. // Update existed crosshair
  4420. if (this.shapes.length > 0) {
  4421. this.shapes[0].options.d = pathH;
  4422. this.shapes[1].options.d = pathV;
  4423. } else {
  4424. // Add new crosshairs
  4425. crosshairOptionsX = merge(defaultOptions, options.crosshairX);
  4426. crosshairOptionsY = merge(defaultOptions, options.crosshairY);
  4427. this.initShape(H.extend({
  4428. d: pathH
  4429. }, crosshairOptionsX), false);
  4430. this.initShape(H.extend({
  4431. d: pathV
  4432. }, crosshairOptionsY), false);
  4433. }
  4434. },
  4435. onDrag: function (e) {
  4436. var translation = this.mouseMoveToTranslation(e),
  4437. selectType = this.options.typeOptions.selectType,
  4438. x = selectType === 'y' ? 0 : translation.x,
  4439. y = selectType === 'x' ? 0 : translation.y;
  4440. this.translate(x, y);
  4441. this.offsetX += x;
  4442. this.offsetY += y;
  4443. // animation, resize, setStartPoints
  4444. this.redraw(false, false, true);
  4445. },
  4446. /**
  4447. * Translate start or end ("left" or "right") side of the measure.
  4448. * Update start points (startXMin, startXMax, startYMin, startYMax)
  4449. *
  4450. * @param {number} dx - the amount of x translation
  4451. * @param {number} dy - the amount of y translation
  4452. * @param {number} cpIndex - index of control point
  4453. * @param {number} selectType - x / y / xy
  4454. */
  4455. resize: function (dx, dy, cpIndex, selectType) {
  4456. // background shape
  4457. var bckShape = this.shapes[2];
  4458. if (selectType === 'x') {
  4459. if (cpIndex === 0) {
  4460. bckShape.translatePoint(dx, 0, 0);
  4461. bckShape.translatePoint(dx, dy, 3);
  4462. } else {
  4463. bckShape.translatePoint(dx, 0, 1);
  4464. bckShape.translatePoint(dx, dy, 2);
  4465. }
  4466. } else if (selectType === 'y') {
  4467. if (cpIndex === 0) {
  4468. bckShape.translatePoint(0, dy, 0);
  4469. bckShape.translatePoint(0, dy, 1);
  4470. } else {
  4471. bckShape.translatePoint(0, dy, 2);
  4472. bckShape.translatePoint(0, dy, 3);
  4473. }
  4474. } else {
  4475. bckShape.translatePoint(dx, 0, 1);
  4476. bckShape.translatePoint(dx, dy, 2);
  4477. bckShape.translatePoint(0, dy, 3);
  4478. }
  4479. this.calculations.updateStartPoints
  4480. .call(this, false, true, cpIndex, dx, dy);
  4481. this.options.typeOptions.background.height = Math.abs(
  4482. this.startYMax - this.startYMin
  4483. );
  4484. this.options.typeOptions.background.width = Math.abs(
  4485. this.startXMax - this.startXMin
  4486. );
  4487. },
  4488. /**
  4489. * Redraw event which render elements and update start points
  4490. * if needed
  4491. *
  4492. * @param {Boolean} animation
  4493. * @param {Boolean} resize - flag if resized
  4494. * @param {Boolean} setStartPoints - update position of start points
  4495. */
  4496. redraw: function (animation, resize, setStartPoints) {
  4497. this.linkPoints();
  4498. if (!this.graphic) {
  4499. this.render();
  4500. }
  4501. if (setStartPoints) {
  4502. this.calculations.updateStartPoints.call(this, true, false);
  4503. }
  4504. this.addValues(resize);
  4505. this.addCrosshairs();
  4506. this.redrawItems(this.shapes, animation);
  4507. this.redrawItems(this.labels, animation);
  4508. // redraw control point to run positioner
  4509. this.controlPoints.forEach(function (controlPoint) {
  4510. controlPoint.redraw();
  4511. });
  4512. },
  4513. translate: function (dx, dy) {
  4514. this.shapes.forEach(function (item) {
  4515. item.translate(dx, dy);
  4516. });
  4517. this.options.typeOptions.point.x = this.startXMin;
  4518. this.options.typeOptions.point.y = this.startYMin;
  4519. },
  4520. calculations: {
  4521. /*
  4522. * Set starting points
  4523. */
  4524. init: function () {
  4525. var options = this.options.typeOptions,
  4526. chart = this.chart,
  4527. getPointPos = this.calculations.getPointPos,
  4528. inverted = chart.options.chart.inverted,
  4529. xAxis = chart.xAxis[options.xAxis],
  4530. yAxis = chart.yAxis[options.yAxis],
  4531. bck = options.background,
  4532. width = inverted ? bck.height : bck.width,
  4533. height = inverted ? bck.width : bck.height,
  4534. selectType = options.selectType,
  4535. top = chart.plotTop,
  4536. left = chart.plotLeft;
  4537. this.startXMin = options.point.x;
  4538. this.startYMin = options.point.y;
  4539. if (isNumber(width)) {
  4540. this.startXMax = this.startXMin + width;
  4541. } else {
  4542. this.startXMax = getPointPos(
  4543. xAxis,
  4544. this.startXMin,
  4545. parseFloat(width)
  4546. );
  4547. }
  4548. if (isNumber(height)) {
  4549. this.startYMax = this.startYMin - height;
  4550. } else {
  4551. this.startYMax = getPointPos(
  4552. yAxis,
  4553. this.startYMin,
  4554. parseFloat(height)
  4555. );
  4556. }
  4557. // x / y selection type
  4558. if (selectType === 'x') {
  4559. this.startYMin = yAxis.toValue(top);
  4560. this.startYMax = yAxis.toValue(top + chart.plotHeight);
  4561. } else if (selectType === 'y') {
  4562. this.startXMin = xAxis.toValue(left);
  4563. this.startXMax = xAxis.toValue(left + chart.plotWidth);
  4564. }
  4565. },
  4566. /*
  4567. * Set current xAxisMin, xAxisMax, yAxisMin, yAxisMax.
  4568. * Calculations of measure values (min, max, average, bins).
  4569. *
  4570. * @param {Boolean} resize - flag if shape is resized
  4571. */
  4572. recalculate: function (resize) {
  4573. var calc = this.calculations,
  4574. options = this.options.typeOptions,
  4575. xAxis = this.chart.xAxis[options.xAxis],
  4576. yAxis = this.chart.yAxis[options.yAxis],
  4577. getPointPos = this.calculations.getPointPos,
  4578. offsetX = this.offsetX,
  4579. offsetY = this.offsetY;
  4580. this.xAxisMin = getPointPos(xAxis, this.startXMin, offsetX);
  4581. this.xAxisMax = getPointPos(xAxis, this.startXMax, offsetX);
  4582. this.yAxisMin = getPointPos(yAxis, this.startYMin, offsetY);
  4583. this.yAxisMax = getPointPos(yAxis, this.startYMax, offsetY);
  4584. this.min = calc.min.call(this);
  4585. this.max = calc.max.call(this);
  4586. this.average = calc.average.call(this);
  4587. this.bins = calc.bins.call(this);
  4588. if (resize) {
  4589. this.resize(0, 0);
  4590. }
  4591. },
  4592. /*
  4593. * Set current xAxisMin, xAxisMax, yAxisMin, yAxisMax.
  4594. * Calculations of measure values (min, max, average, bins).
  4595. *
  4596. * @param {Object} axis - x or y axis reference
  4597. * @param {Number} value - point's value (x or y)
  4598. * @param {Number} offset - amount of pixels
  4599. */
  4600. getPointPos: function (axis, value, offset) {
  4601. return axis.toValue(
  4602. axis.toPixels(value) + offset
  4603. );
  4604. },
  4605. /*
  4606. * Update position of start points
  4607. * (startXMin, startXMax, startYMin, startYMax)
  4608. *
  4609. * @param {Boolean} redraw - flag if shape is redraw
  4610. * @param {Boolean} resize - flag if shape is resized
  4611. * @param {Boolean} cpIndex - index of controlPoint
  4612. */
  4613. updateStartPoints: function (redraw, resize, cpIndex, dx, dy) {
  4614. var options = this.options.typeOptions,
  4615. selectType = options.selectType,
  4616. xAxis = this.chart.xAxis[options.xAxis],
  4617. yAxis = this.chart.yAxis[options.yAxis],
  4618. getPointPos = this.calculations.getPointPos,
  4619. startXMin = this.startXMin,
  4620. startXMax = this.startXMax,
  4621. startYMin = this.startYMin,
  4622. startYMax = this.startYMax,
  4623. offsetX = this.offsetX,
  4624. offsetY = this.offsetY;
  4625. if (resize) {
  4626. if (selectType === 'x') {
  4627. if (cpIndex === 0) {
  4628. this.startXMin = getPointPos(xAxis, startXMin, dx);
  4629. } else {
  4630. this.startXMax = getPointPos(xAxis, startXMax, dx);
  4631. }
  4632. } else if (selectType === 'y') {
  4633. if (cpIndex === 0) {
  4634. this.startYMin = getPointPos(yAxis, startYMin, dy);
  4635. } else {
  4636. this.startYMax = getPointPos(yAxis, startYMax, dy);
  4637. }
  4638. } else {
  4639. this.startXMax = getPointPos(xAxis, startXMax, dx);
  4640. this.startYMax = getPointPos(yAxis, startYMax, dy);
  4641. }
  4642. }
  4643. if (redraw) {
  4644. this.startXMin = getPointPos(xAxis, startXMin, offsetX);
  4645. this.startXMax = getPointPos(xAxis, startXMax, offsetX);
  4646. this.startYMin = getPointPos(yAxis, startYMin, offsetY);
  4647. this.startYMax = getPointPos(yAxis, startYMax, offsetY);
  4648. this.offsetX = 0;
  4649. this.offsetY = 0;
  4650. }
  4651. },
  4652. /*
  4653. * Default formatter of label's content
  4654. */
  4655. defaultFormatter: function () {
  4656. return 'Min: ' + this.min +
  4657. '<br>Max: ' + this.max +
  4658. '<br>Average: ' + this.average +
  4659. '<br>Bins: ' + this.bins;
  4660. },
  4661. /*
  4662. * Set values for xAxisMin, xAxisMax, yAxisMin, yAxisMax, also
  4663. * when chart is inverted
  4664. */
  4665. getExtremes: function (xAxisMin, xAxisMax, yAxisMin, yAxisMax) {
  4666. return {
  4667. xAxisMin: Math.min(xAxisMax, xAxisMin),
  4668. xAxisMax: Math.max(xAxisMax, xAxisMin),
  4669. yAxisMin: Math.min(yAxisMax, yAxisMin),
  4670. yAxisMax: Math.max(yAxisMax, yAxisMin)
  4671. };
  4672. },
  4673. /*
  4674. * Definitions of calculations (min, max, average, bins)
  4675. */
  4676. min: function () {
  4677. var min = Infinity,
  4678. series = this.chart.series,
  4679. ext = this.calculations.getExtremes(
  4680. this.xAxisMin,
  4681. this.xAxisMax,
  4682. this.yAxisMin,
  4683. this.yAxisMax
  4684. ),
  4685. isCalculated = false; // to avoid Infinity in formatter
  4686. series.forEach(function (serie) {
  4687. if (
  4688. serie.visible &&
  4689. serie.options.id !== 'highcharts-navigator-series'
  4690. ) {
  4691. serie.points.forEach(function (point) {
  4692. if (
  4693. !point.isNull &&
  4694. point.y < min &&
  4695. point.x > ext.xAxisMin &&
  4696. point.x <= ext.xAxisMax &&
  4697. point.y > ext.yAxisMin &&
  4698. point.y <= ext.yAxisMax
  4699. ) {
  4700. min = point.y;
  4701. isCalculated = true;
  4702. }
  4703. });
  4704. }
  4705. });
  4706. if (!isCalculated) {
  4707. min = '';
  4708. }
  4709. return min;
  4710. },
  4711. max: function () {
  4712. var max = -Infinity,
  4713. series = this.chart.series,
  4714. ext = this.calculations.getExtremes(
  4715. this.xAxisMin,
  4716. this.xAxisMax,
  4717. this.yAxisMin,
  4718. this.yAxisMax
  4719. ),
  4720. isCalculated = false; // to avoid Infinity in formatter
  4721. series.forEach(function (serie) {
  4722. if (
  4723. serie.visible &&
  4724. serie.options.id !== 'highcharts-navigator-series'
  4725. ) {
  4726. serie.points.forEach(function (point) {
  4727. if (
  4728. !point.isNull &&
  4729. point.y > max &&
  4730. point.x > ext.xAxisMin &&
  4731. point.x <= ext.xAxisMax &&
  4732. point.y > ext.yAxisMin &&
  4733. point.y <= ext.yAxisMax
  4734. ) {
  4735. max = point.y;
  4736. isCalculated = true;
  4737. }
  4738. });
  4739. }
  4740. });
  4741. if (!isCalculated) {
  4742. max = '';
  4743. }
  4744. return max;
  4745. },
  4746. average: function () {
  4747. var average = '';
  4748. if (this.max !== '' && this.min !== '') {
  4749. average = (this.max + this.min) / 2;
  4750. }
  4751. return average;
  4752. },
  4753. bins: function () {
  4754. var bins = 0,
  4755. series = this.chart.series,
  4756. ext = this.calculations.getExtremes(
  4757. this.xAxisMin,
  4758. this.xAxisMax,
  4759. this.yAxisMin,
  4760. this.yAxisMax
  4761. ),
  4762. isCalculated = false; // to avoid Infinity in formatter
  4763. series.forEach(function (serie) {
  4764. if (
  4765. serie.visible &&
  4766. serie.options.id !== 'highcharts-navigator-series'
  4767. ) {
  4768. serie.points.forEach(function (point) {
  4769. if (
  4770. !point.isNull &&
  4771. point.x > ext.xAxisMin &&
  4772. point.x <= ext.xAxisMax &&
  4773. point.y > ext.yAxisMin &&
  4774. point.y <= ext.yAxisMax
  4775. ) {
  4776. bins++;
  4777. isCalculated = true;
  4778. }
  4779. });
  4780. }
  4781. });
  4782. if (!isCalculated) {
  4783. bins = '';
  4784. }
  4785. return bins;
  4786. }
  4787. }
  4788. },
  4789. /**
  4790. * A measure annotation.
  4791. *
  4792. * @extends annotations.crookedLine
  4793. * @excluding labels, labelOptions, shapes, shapeOptions
  4794. * @sample highcharts/annotations-advanced/measure/
  4795. * Measure
  4796. * @product highstock
  4797. * @optionparent annotations.measure
  4798. */
  4799. {
  4800. typeOptions: {
  4801. /**
  4802. * Decides in what dimensions the user can resize by dragging the
  4803. * mouse. Can be one of x, y or xy.
  4804. */
  4805. selectType: 'xy',
  4806. /**
  4807. * This number defines which xAxis the point is connected to.
  4808. * It refers to either the axis id or the index of the axis
  4809. * in the xAxis array.
  4810. */
  4811. xAxis: 0,
  4812. /**
  4813. * This number defines which yAxis the point is connected to.
  4814. * It refers to either the axis id or the index of the axis
  4815. * in the yAxis array.
  4816. */
  4817. yAxis: 0,
  4818. background: {
  4819. /**
  4820. * The color of the rectangle.
  4821. */
  4822. fill: 'rgba(130, 170, 255, 0.4)',
  4823. /**
  4824. * The width of border.
  4825. */
  4826. strokeWidth: 0,
  4827. /**
  4828. * The color of border.
  4829. */
  4830. stroke: undefined
  4831. },
  4832. /**
  4833. * Configure a crosshair that is horizontally placed in middle of
  4834. * rectangle.
  4835. *
  4836. */
  4837. crosshairX: {
  4838. /**
  4839. * Enable or disable the horizontal crosshair.
  4840. *
  4841. */
  4842. enabled: true,
  4843. /**
  4844. * The Z index of the crosshair in annotation.
  4845. */
  4846. zIndex: 6,
  4847. /**
  4848. * The dash or dot style of the crosshair's line. For possible
  4849. * values, see
  4850. * [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/).
  4851. *
  4852. * @type {Highcharts.DashStyleType}
  4853. * @default Dash
  4854. */
  4855. dashStyle: 'Dash',
  4856. /**
  4857. * The marker-end defines the arrowhead that will be drawn
  4858. * at the final vertex of the given crosshair's path.
  4859. *
  4860. * @type {string}
  4861. * @default arrow
  4862. */
  4863. markerEnd: 'arrow'
  4864. },
  4865. /**
  4866. * Configure a crosshair that is vertically placed in middle of
  4867. * rectangle.
  4868. */
  4869. crosshairY: {
  4870. /**
  4871. * Enable or disable the vertical crosshair.
  4872. *
  4873. */
  4874. enabled: true,
  4875. /**
  4876. * The Z index of the crosshair in annotation.
  4877. */
  4878. zIndex: 6,
  4879. /**
  4880. * The dash or dot style of the crosshair's line. For possible
  4881. * values, see [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/).
  4882. *
  4883. * @type {Highcharts.DashStyleType}
  4884. * @default Dash
  4885. * @apioption annotations.measure.typeOptions.crosshairY.dashStyle
  4886. *
  4887. */
  4888. dashStyle: 'Dash',
  4889. /**
  4890. * The marker-end defines the arrowhead that will be drawn
  4891. * at the final vertex of the given crosshair's path.
  4892. *
  4893. * @type {string}
  4894. * @default arrow
  4895. * @validvalue ["none", "arrow"]
  4896. *
  4897. */
  4898. markerEnd: 'arrow'
  4899. },
  4900. label: {
  4901. /**
  4902. * Enable or disable the label text (min, max, average,
  4903. * bins values).
  4904. *
  4905. * Defaults to true.
  4906. */
  4907. enabled: true,
  4908. /**
  4909. * CSS styles for the measure label.
  4910. *
  4911. * @type {Highcharts.CSSObject}
  4912. * @default {"color": "#666666", "fontSize": "11px"}
  4913. */
  4914. style: {
  4915. fontSize: '11px',
  4916. color: '#666666'
  4917. },
  4918. /**
  4919. * Formatter function for the label text.
  4920. *
  4921. * Available data are:
  4922. *
  4923. * <table>
  4924. *
  4925. * <tbody>
  4926. *
  4927. * <tr>
  4928. *
  4929. * <td>`this.min`</td>
  4930. *
  4931. * <td>The mininimum value of the points in the selected
  4932. * range.</td>
  4933. *
  4934. * </tr>
  4935. *
  4936. * <tr>
  4937. *
  4938. * <td>`this.max`</td>
  4939. *
  4940. * <td>The maximum value of the points in the selected
  4941. * range.</td>
  4942. *
  4943. * </tr>
  4944. *
  4945. * <tr>
  4946. *
  4947. * <td>`this.average`</td>
  4948. *
  4949. * <td>The average value of the points in the selected
  4950. * range.</td>
  4951. *
  4952. * </tr>
  4953. *
  4954. * <tr>
  4955. *
  4956. * <td>`this.bins`</td>
  4957. *
  4958. * <td>The amount of the points in the selected range.</td>
  4959. *
  4960. * </tr>
  4961. *
  4962. * </table>
  4963. *
  4964. * @type {function}
  4965. *
  4966. */
  4967. formatter: undefined
  4968. }
  4969. },
  4970. controlPointOptions: {
  4971. positioner: function (target) {
  4972. var cpIndex = this.index,
  4973. chart = target.chart,
  4974. options = target.options,
  4975. typeOptions = options.typeOptions,
  4976. selectType = typeOptions.selectType,
  4977. controlPointOptions = options.controlPointOptions,
  4978. inverted = chart.options.chart.inverted,
  4979. xAxis = chart.xAxis[typeOptions.xAxis],
  4980. yAxis = chart.yAxis[typeOptions.yAxis],
  4981. targetX = target.xAxisMax,
  4982. targetY = target.yAxisMax,
  4983. ext = target.calculations.getExtremes(
  4984. target.xAxisMin,
  4985. target.xAxisMax,
  4986. target.yAxisMin,
  4987. target.yAxisMax
  4988. ),
  4989. x, y;
  4990. if (selectType === 'x') {
  4991. targetY = (ext.yAxisMax - ext.yAxisMin) / 2;
  4992. // first control point
  4993. if (cpIndex === 0) {
  4994. targetX = target.xAxisMin;
  4995. }
  4996. }
  4997. if (selectType === 'y') {
  4998. targetX = ext.xAxisMin +
  4999. ((ext.xAxisMax - ext.xAxisMin) / 2);
  5000. // first control point
  5001. if (cpIndex === 0) {
  5002. targetY = target.yAxisMin;
  5003. }
  5004. }
  5005. if (inverted) {
  5006. x = yAxis.toPixels(targetY);
  5007. y = xAxis.toPixels(targetX);
  5008. } else {
  5009. x = xAxis.toPixels(targetX);
  5010. y = yAxis.toPixels(targetY);
  5011. }
  5012. return {
  5013. x: x - (controlPointOptions.width / 2),
  5014. y: y - (controlPointOptions.height / 2)
  5015. };
  5016. },
  5017. events: {
  5018. drag: function (e, target) {
  5019. var translation = this.mouseMoveToTranslation(e),
  5020. selectType = target.options.typeOptions.selectType,
  5021. index = this.index,
  5022. x = selectType === 'y' ? 0 : translation.x,
  5023. y = selectType === 'x' ? 0 : translation.y;
  5024. target.resize(
  5025. x,
  5026. y,
  5027. index,
  5028. selectType
  5029. );
  5030. target.resizeX += x;
  5031. target.resizeY += y;
  5032. target.redraw(false, true);
  5033. }
  5034. }
  5035. }
  5036. });
  5037. Annotation.types.measure = Measure;
  5038. return Measure;
  5039. }(Highcharts));
  5040. var chartNavigation = (function () {
  5041. /**
  5042. * (c) 2010-2018 Paweł Fus
  5043. *
  5044. * License: www.highcharts.com/license
  5045. */
  5046. var chartNavigation = {
  5047. /**
  5048. * Initializes `chart.navigation` object which delegates `update()` methods
  5049. * to all other common classes (used in exporting and navigationBindings).
  5050. *
  5051. * @private
  5052. *
  5053. * @param {Highcharts.Chart} chart
  5054. * The chart instance.
  5055. */
  5056. initUpdate: function (chart) {
  5057. if (!chart.navigation) {
  5058. chart.navigation = {
  5059. updates: [],
  5060. update: function (options, redraw) {
  5061. this.updates.forEach(function (updateConfig) {
  5062. updateConfig.update.call(
  5063. updateConfig.context,
  5064. options,
  5065. redraw
  5066. );
  5067. });
  5068. }
  5069. };
  5070. }
  5071. },
  5072. /**
  5073. * Registers an `update()` method in the `chart.navigation` object.
  5074. *
  5075. * @private
  5076. *
  5077. * @param {function} update
  5078. * The `update()` method that will be called in `chart.update()`.
  5079. *
  5080. * @param {Highcharts.Chart} chart
  5081. * The chart instance. `update()` will use that as a context
  5082. * (`this`).
  5083. */
  5084. addUpdate: function (update, chart) {
  5085. if (!chart.navigation) {
  5086. this.initUpdate(chart);
  5087. }
  5088. chart.navigation.updates.push({
  5089. update: update,
  5090. context: chart
  5091. });
  5092. }
  5093. };
  5094. return chartNavigation;
  5095. }());
  5096. (function (H, chartNavigationMixin) {
  5097. /**
  5098. * (c) 2009-2017 Highsoft, Black Label
  5099. *
  5100. * License: www.highcharts.com/license
  5101. */
  5102. var doc = H.doc,
  5103. addEvent = H.addEvent,
  5104. pick = H.pick,
  5105. merge = H.merge,
  5106. extend = H.extend,
  5107. isNumber = H.isNumber,
  5108. fireEvent = H.fireEvent,
  5109. isArray = H.isArray,
  5110. isObject = H.isObject,
  5111. objectEach = H.objectEach,
  5112. PREFIX = 'highcharts-';
  5113. /**
  5114. * @private
  5115. * @interface bindingsUtils
  5116. */
  5117. var bindingsUtils = {
  5118. /**
  5119. * Update size of background (rect) in some annotations: Measure, Simple
  5120. * Rect.
  5121. *
  5122. * @private
  5123. * @function bindingsUtils.updateRectSize
  5124. *
  5125. * @param {global.Event} event
  5126. * Normalized browser event
  5127. *
  5128. * @param {Highcharts.Annotation} annotation
  5129. * Annotation to be updated
  5130. */
  5131. updateRectSize: function (event, annotation) {
  5132. var options = annotation.options.typeOptions,
  5133. x = this.chart.xAxis[0].toValue(event.chartX),
  5134. y = this.chart.yAxis[0].toValue(event.chartY),
  5135. width = x - options.point.x,
  5136. height = options.point.y - y;
  5137. annotation.update({
  5138. typeOptions: {
  5139. background: {
  5140. width: width,
  5141. height: height
  5142. }
  5143. }
  5144. });
  5145. },
  5146. /**
  5147. * Get field type according to value
  5148. *
  5149. * @private
  5150. * @function bindingsUtils.getFieldType
  5151. *
  5152. * @param {*} value
  5153. * Atomic type (one of: string, number, boolean)
  5154. *
  5155. * @return {string}
  5156. * Field type (one of: text, number, checkbox)
  5157. */
  5158. getFieldType: function (value) {
  5159. return {
  5160. 'string': 'text',
  5161. 'number': 'number',
  5162. 'boolean': 'checkbox'
  5163. }[typeof value];
  5164. }
  5165. };
  5166. H.NavigationBindings = function (chart, options) {
  5167. this.chart = chart;
  5168. this.options = options;
  5169. this.eventsToUnbind = [];
  5170. this.container = doc.getElementsByClassName(
  5171. this.options.bindingsClassName
  5172. );
  5173. };
  5174. // Define which options from annotations should show up in edit box:
  5175. H.NavigationBindings.annotationsEditable = {
  5176. // `typeOptions` are always available
  5177. // Nested and shared options:
  5178. nestedOptions: {
  5179. labelOptions: ['style', 'format', 'backgroundColor'],
  5180. labels: ['style'],
  5181. label: ['style'],
  5182. style: ['fontSize', 'color'],
  5183. background: ['fill', 'strokeWidth', 'stroke'],
  5184. innerBackground: ['fill', 'strokeWidth', 'stroke'],
  5185. outerBackground: ['fill', 'strokeWidth', 'stroke'],
  5186. shapeOptions: ['fill', 'strokeWidth', 'stroke'],
  5187. shapes: ['fill', 'strokeWidth', 'stroke'],
  5188. line: ['strokeWidth', 'stroke'],
  5189. backgroundColors: [true],
  5190. connector: ['fill', 'strokeWidth', 'stroke'],
  5191. crosshairX: ['strokeWidth', 'stroke'],
  5192. crosshairY: ['strokeWidth', 'stroke']
  5193. },
  5194. // Simple shapes:
  5195. circle: ['shapes'],
  5196. verticalLine: [],
  5197. label: ['labelOptions'],
  5198. // Measure
  5199. measure: ['background', 'crosshairY', 'crosshairX'],
  5200. // Others:
  5201. fibonacci: [],
  5202. tunnel: ['background', 'line', 'height'],
  5203. pitchfork: ['innerBackground', 'outerBackground'],
  5204. rect: ['shapes'],
  5205. // Crooked lines, elliots, arrows etc:
  5206. crookedLine: []
  5207. };
  5208. // Define non editable fields per annotation, for example Rectangle inherits
  5209. // options from Measure, but crosshairs are not available
  5210. H.NavigationBindings.annotationsNonEditable = {
  5211. rectangle: ['crosshairX', 'crosshairY', 'label']
  5212. };
  5213. extend(H.NavigationBindings.prototype, {
  5214. // Private properties added by bindings:
  5215. // Active (selected) annotation that is editted through popup/forms
  5216. // activeAnnotation: Annotation
  5217. // Holder for current step, used on mouse move to update bound object
  5218. // mouseMoveEvent: function () {}
  5219. // Next event in `step` array to be called on chart's click
  5220. // nextEvent: function () {}
  5221. // Index in the `step` array of the current event
  5222. // stepIndex: 0
  5223. // Flag to determine if current binding has steps
  5224. // steps: true|false
  5225. // Bindings holder for all events
  5226. // selectedButton: {}
  5227. // Holder for user options, returned from `start` event, and passed on to
  5228. // `step`'s' and `end`.
  5229. // currentUserDetails: {}
  5230. /**
  5231. * Initi all events conencted to NavigationBindings.
  5232. *
  5233. * @private
  5234. * @function Highcharts.NavigationBindings#initEvents
  5235. */
  5236. initEvents: function () {
  5237. var navigation = this,
  5238. chart = navigation.chart,
  5239. bindingsContainer = navigation.container,
  5240. options = navigation.options;
  5241. // Shorthand object for getting events for buttons:
  5242. navigation.boundClassNames = {};
  5243. objectEach(options.bindings, function (value) {
  5244. navigation.boundClassNames[value.className] = value;
  5245. });
  5246. // Handle multiple containers with the same class names:
  5247. [].forEach.call(bindingsContainer, function (subContainer) {
  5248. navigation.eventsToUnbind.push(
  5249. addEvent(
  5250. subContainer,
  5251. 'click',
  5252. function (event) {
  5253. var bindings = navigation.getButtonEvents(
  5254. bindingsContainer,
  5255. event
  5256. );
  5257. if (bindings) {
  5258. navigation.bindingsButtonClick(
  5259. bindings.button,
  5260. bindings.events,
  5261. event
  5262. );
  5263. }
  5264. }
  5265. )
  5266. );
  5267. });
  5268. objectEach(options.events || {}, function (callback, eventName) {
  5269. navigation.eventsToUnbind.push(
  5270. addEvent(
  5271. navigation,
  5272. eventName,
  5273. callback
  5274. )
  5275. );
  5276. });
  5277. navigation.eventsToUnbind.push(
  5278. addEvent(chart.container, 'click', function (e) {
  5279. if (
  5280. !chart.cancelClick &&
  5281. chart.isInsidePlot(
  5282. e.chartX - chart.plotLeft,
  5283. e.chartY - chart.plotTop
  5284. )
  5285. ) {
  5286. navigation.bindingsChartClick(this, e);
  5287. }
  5288. })
  5289. );
  5290. navigation.eventsToUnbind.push(
  5291. addEvent(chart.container, 'mousemove', function (e) {
  5292. navigation.bindingsContainerMouseMove(this, e);
  5293. })
  5294. );
  5295. },
  5296. /**
  5297. * Common chart.update() delegation, shared between bindings and exporting.
  5298. *
  5299. * @private
  5300. * @function Highcharts.NavigationBindings#initUpdate
  5301. */
  5302. initUpdate: function () {
  5303. var navigation = this;
  5304. chartNavigationMixin.addUpdate(
  5305. function (options) {
  5306. navigation.update(options);
  5307. },
  5308. this.chart
  5309. );
  5310. },
  5311. /**
  5312. * Hook for click on a button, method selcts/unselects buttons,
  5313. * then calls `bindings.init` callback.
  5314. *
  5315. * @private
  5316. * @function Highcharts.NavigationBindings#bindingsButtonClick
  5317. *
  5318. * @param {Highcharts.HTMLDOMElement} [button]
  5319. * Clicked button
  5320. *
  5321. * @param {object} [events]
  5322. * Events passed down from bindings (`init`, `start`, `step`, `end`)
  5323. *
  5324. * @param {global.Event} [clickEvent]
  5325. * Browser's click event
  5326. */
  5327. bindingsButtonClick: function (button, events, clickEvent) {
  5328. var navigation = this,
  5329. chart = navigation.chart;
  5330. if (navigation.selectedButtonElement) {
  5331. fireEvent(
  5332. navigation,
  5333. 'deselectButton',
  5334. { button: navigation.selectedButtonElement }
  5335. );
  5336. if (navigation.nextEvent) {
  5337. // Remove in-progress annotations adders:
  5338. if (
  5339. navigation.currentUserDetails &&
  5340. navigation.currentUserDetails.coll === 'annotations'
  5341. ) {
  5342. chart.removeAnnotation(navigation.currentUserDetails);
  5343. }
  5344. navigation.mouseMoveEvent = navigation.nextEvent = false;
  5345. }
  5346. }
  5347. navigation.selectedButton = events;
  5348. navigation.selectedButtonElement = button;
  5349. fireEvent(navigation, 'selectButton', { button: button });
  5350. // Call "init" event, for example to open modal window
  5351. if (events.init) {
  5352. events.init.call(navigation, button, clickEvent);
  5353. }
  5354. if (events.start || events.steps) {
  5355. chart.renderer.boxWrapper.addClass(PREFIX + 'draw-mode');
  5356. }
  5357. },
  5358. /**
  5359. * Hook for click on a chart, first click on a chart calls `start` event,
  5360. * then on all subsequent clicks iterate over `steps` array.
  5361. * When finished, calls `end` event.
  5362. *
  5363. * @private
  5364. * @function Highcharts.NavigationBindings#bindingsChartClick
  5365. *
  5366. * @param {Highcharts.Chart} chart
  5367. * Chart that click was performed on.
  5368. *
  5369. * @param {global.Event} clickEvent
  5370. * Browser's click event.
  5371. */
  5372. bindingsChartClick: function (chartContainer, clickEvent) {
  5373. var navigation = this,
  5374. chart = navigation.chart,
  5375. selectedButton = navigation.selectedButton,
  5376. svgContainer = chart.renderer.boxWrapper;
  5377. if (
  5378. navigation.activeAnnotation &&
  5379. !clickEvent.activeAnnotation &&
  5380. // Element could be removed in the child action, e.g. button
  5381. clickEvent.target.parentNode &&
  5382. // TO DO: Polyfill for IE11?
  5383. !clickEvent.target.closest('.' + PREFIX + 'popup')
  5384. ) {
  5385. fireEvent(navigation, 'closePopup');
  5386. navigation.deselectAnnotation();
  5387. }
  5388. if (!selectedButton || !selectedButton.start) {
  5389. return;
  5390. }
  5391. if (!navigation.nextEvent) {
  5392. // Call init method:
  5393. navigation.currentUserDetails = selectedButton.start.call(
  5394. navigation,
  5395. clickEvent
  5396. );
  5397. // If steps exists (e.g. Annotations), bind them:
  5398. if (selectedButton.steps) {
  5399. navigation.stepIndex = 0;
  5400. navigation.steps = true;
  5401. navigation.mouseMoveEvent = navigation.nextEvent =
  5402. selectedButton.steps[navigation.stepIndex];
  5403. } else {
  5404. fireEvent(
  5405. navigation,
  5406. 'deselectButton',
  5407. { button: navigation.selectedButtonElement }
  5408. );
  5409. svgContainer.removeClass(PREFIX + 'draw-mode');
  5410. navigation.steps = false;
  5411. navigation.selectedButton = null;
  5412. // First click is also the last one:
  5413. if (selectedButton.end) {
  5414. selectedButton.end.call(
  5415. navigation,
  5416. clickEvent,
  5417. navigation.currentUserDetails
  5418. );
  5419. }
  5420. }
  5421. } else {
  5422. navigation.nextEvent(
  5423. clickEvent,
  5424. navigation.currentUserDetails
  5425. );
  5426. if (navigation.steps) {
  5427. navigation.stepIndex++;
  5428. if (selectedButton.steps[navigation.stepIndex]) {
  5429. // If we have more steps, bind them one by one:
  5430. navigation.mouseMoveEvent = navigation.nextEvent =
  5431. selectedButton.steps[navigation.stepIndex];
  5432. } else {
  5433. fireEvent(
  5434. navigation,
  5435. 'deselectButton',
  5436. { button: navigation.selectedButtonElement }
  5437. );
  5438. svgContainer.removeClass(PREFIX + 'draw-mode');
  5439. // That was the last step, call end():
  5440. if (selectedButton.end) {
  5441. selectedButton.end.call(
  5442. navigation,
  5443. clickEvent,
  5444. navigation.currentUserDetails
  5445. );
  5446. }
  5447. navigation.nextEvent = false;
  5448. navigation.mouseMoveEvent = false;
  5449. navigation.selectedButton = null;
  5450. }
  5451. }
  5452. }
  5453. },
  5454. /**
  5455. * Hook for mouse move on a chart's container. It calls current step.
  5456. *
  5457. * @private
  5458. * @function Highcharts.NavigationBindings#bindingsContainerMouseMove
  5459. *
  5460. * @param {Highcharts.HTMLDOMElement} container
  5461. * Chart's container.
  5462. *
  5463. * @param {global.Event} moveEvent
  5464. * Browser's move event.
  5465. */
  5466. bindingsContainerMouseMove: function (container, moveEvent) {
  5467. if (this.mouseMoveEvent) {
  5468. this.mouseMoveEvent(
  5469. moveEvent,
  5470. this.currentUserDetails
  5471. );
  5472. }
  5473. },
  5474. /**
  5475. * Translate fields (e.g. `params.period` or `marker.styles.color`) to
  5476. * Highcharts options object (e.g. `{ params: { period } }`).
  5477. *
  5478. * @private
  5479. * @function Highcharts.NavigationBindings#fieldsToOptions
  5480. *
  5481. * @param {object} fields
  5482. * Fields from popup form.
  5483. *
  5484. * @param {object} config
  5485. * Default config to be modified.
  5486. *
  5487. * @return {object}
  5488. * Modified config
  5489. */
  5490. fieldsToOptions: function (fields, config) {
  5491. objectEach(fields, function (value, field) {
  5492. var parsedValue = parseFloat(value),
  5493. path = field.split('.'),
  5494. parent = config,
  5495. pathLength = path.length - 1;
  5496. // If it's a number (not "forma" options), parse it:
  5497. if (
  5498. isNumber(parsedValue) &&
  5499. !value.match(/px/g) &&
  5500. !field.match(/format/g)
  5501. ) {
  5502. value = parsedValue;
  5503. }
  5504. // Remove empty strings or values like 0
  5505. if (value !== '' && value !== 'undefined') {
  5506. path.forEach(function (name, index) {
  5507. var nextName = pick(path[index + 1], '');
  5508. if (pathLength === index) {
  5509. // Last index, put value:
  5510. parent[name] = value;
  5511. } else if (!parent[name]) {
  5512. // Create middle property:
  5513. parent[name] = nextName.match(/\d/g) ? [] : {};
  5514. parent = parent[name];
  5515. } else {
  5516. // Jump into next property
  5517. parent = parent[name];
  5518. }
  5519. });
  5520. }
  5521. });
  5522. return config;
  5523. },
  5524. /**
  5525. * Shorthand method to deselect an annotation.
  5526. *
  5527. * @function Highcharts.NavigationBindings#deselectAnnotation
  5528. */
  5529. deselectAnnotation: function () {
  5530. if (this.activeAnnotation) {
  5531. this.activeAnnotation.setControlPointsVisibility(false);
  5532. this.activeAnnotation = false;
  5533. }
  5534. },
  5535. /**
  5536. * Generates API config for popup in the same format as options for
  5537. * Annotation object.
  5538. *
  5539. * @function Highcharts.NavigationBindings#annotationToFields
  5540. *
  5541. * @param {Highcharts.Annotation} annotation
  5542. * Annotations object
  5543. *
  5544. * @return {object}
  5545. * Annotation options to be displayed in popup box
  5546. */
  5547. annotationToFields: function (annotation) {
  5548. var options = annotation.options,
  5549. editables = H.NavigationBindings.annotationsEditable,
  5550. nestedEditables = editables.nestedOptions,
  5551. getFieldType = this.utils.getFieldType,
  5552. type = pick(
  5553. options.type,
  5554. options.shapes && options.shapes[0] &&
  5555. options.shapes[0].type,
  5556. options.labels && options.labels[0] &&
  5557. options.labels[0].itemType,
  5558. 'label'
  5559. ),
  5560. nonEditables = H.NavigationBindings
  5561. .annotationsNonEditable[options.langKey] || [],
  5562. visualOptions = {
  5563. langKey: options.langKey,
  5564. type: type
  5565. };
  5566. /**
  5567. * Nested options traversing. Method goes down to the options and copies
  5568. * allowed options (with values) to new object, which is last parameter:
  5569. * "parent".
  5570. *
  5571. * @private
  5572. * @function Highcharts.NavigationBindings#annotationToFields.traverse
  5573. *
  5574. * @param {*} option
  5575. * Atomic type or object/array
  5576. *
  5577. * @param {string} key
  5578. * Option name, for example "visible" or "x", "y"
  5579. *
  5580. * @param {object} allowed
  5581. * Editables from H.NavigationBindings.annotationsEditable
  5582. *
  5583. * @param {object} parent
  5584. * Where new options will be assigned
  5585. */
  5586. function traverse(option, key, parentEditables, parent) {
  5587. var nextParent;
  5588. if (
  5589. parentEditables &&
  5590. nonEditables.indexOf(key) === -1 &&
  5591. (
  5592. (
  5593. parentEditables.indexOf &&
  5594. parentEditables.indexOf(key)
  5595. ) >= 0 ||
  5596. parentEditables[key] || // nested array
  5597. parentEditables === true // simple array
  5598. )
  5599. ) {
  5600. // Roots:
  5601. if (isArray(option)) {
  5602. parent[key] = [];
  5603. option.forEach(function (arrayOption, i) {
  5604. if (!isObject(arrayOption)) {
  5605. // Simple arrays, e.g. [String, Number, Boolean]
  5606. traverse(
  5607. arrayOption,
  5608. 0,
  5609. nestedEditables[key],
  5610. parent[key]
  5611. );
  5612. } else {
  5613. // Advanced arrays, e.g. [Object, Object]
  5614. parent[key][i] = {};
  5615. objectEach(
  5616. arrayOption,
  5617. function (nestedOption, nestedKey) {
  5618. traverse(
  5619. nestedOption,
  5620. nestedKey,
  5621. nestedEditables[key],
  5622. parent[key][i]
  5623. );
  5624. }
  5625. );
  5626. }
  5627. });
  5628. } else if (isObject(option)) {
  5629. nextParent = {};
  5630. if (isArray(parent)) {
  5631. parent.push(nextParent);
  5632. nextParent[key] = {};
  5633. nextParent = nextParent[key];
  5634. } else {
  5635. parent[key] = nextParent;
  5636. }
  5637. objectEach(option, function (nestedOption, nestedKey) {
  5638. traverse(
  5639. nestedOption,
  5640. nestedKey,
  5641. key === 0 ? parentEditables : nestedEditables[key],
  5642. nextParent
  5643. );
  5644. });
  5645. } else {
  5646. // Leaf:
  5647. if (key === 'format') {
  5648. parent[key] = [
  5649. H.format(
  5650. option,
  5651. annotation.labels[0].points[0]
  5652. ).toString(),
  5653. 'text'
  5654. ];
  5655. } else if (isArray(parent)) {
  5656. parent.push([option, getFieldType(option)]);
  5657. } else {
  5658. parent[key] = [option, getFieldType(option)];
  5659. }
  5660. }
  5661. }
  5662. }
  5663. objectEach(options, function (option, key) {
  5664. if (key === 'typeOptions') {
  5665. visualOptions[key] = {};
  5666. objectEach(options[key], function (typeOption, typeKey) {
  5667. traverse(
  5668. typeOption,
  5669. typeKey,
  5670. nestedEditables,
  5671. visualOptions[key],
  5672. true
  5673. );
  5674. });
  5675. } else {
  5676. traverse(option, key, editables[type], visualOptions);
  5677. }
  5678. });
  5679. return visualOptions;
  5680. },
  5681. /**
  5682. * Get all class names for all parents in the element. Iterates until finds
  5683. * main container.
  5684. *
  5685. * @function Highcharts.NavigationBindings#getClickedClassNames
  5686. *
  5687. * @param {Highcharts.HTMLDOMElement}
  5688. * Container that event is bound to.
  5689. *
  5690. * @param {global.Event} event
  5691. * Browser's event.
  5692. *
  5693. * @return {Array<string>}
  5694. * Array of class names with corresponding elements
  5695. */
  5696. getClickedClassNames: function (container, event) {
  5697. var element = event.target,
  5698. classNames = [],
  5699. elemClassName;
  5700. while (element) {
  5701. elemClassName = H.attr(element, 'class');
  5702. if (elemClassName) {
  5703. classNames = classNames.concat(
  5704. elemClassName.split(' ').map(
  5705. function (name) { // eslint-disable-line no-loop-func
  5706. return [
  5707. name,
  5708. element
  5709. ];
  5710. }
  5711. )
  5712. );
  5713. }
  5714. element = element.parentNode;
  5715. if (element === container) {
  5716. return classNames;
  5717. }
  5718. }
  5719. return classNames;
  5720. },
  5721. /**
  5722. * Get events bound to a button. It's a custom event delegation to find all
  5723. * events connected to the element.
  5724. *
  5725. * @function Highcharts.NavigationBindings#getButtonEvents
  5726. *
  5727. * @param {Highcharts.HTMLDOMElement}
  5728. * Container that event is bound to.
  5729. *
  5730. * @param {global.Event} event
  5731. * Browser's event.
  5732. *
  5733. * @return {object}
  5734. * Oject with events (init, start, steps, and end)
  5735. */
  5736. getButtonEvents: function (container, event) {
  5737. var navigation = this,
  5738. classNames = this.getClickedClassNames(container, event),
  5739. bindings;
  5740. classNames.forEach(function (className) {
  5741. if (navigation.boundClassNames[className[0]] && !bindings) {
  5742. bindings = {
  5743. events: navigation.boundClassNames[className[0]],
  5744. button: className[1]
  5745. };
  5746. }
  5747. });
  5748. return bindings;
  5749. },
  5750. /**
  5751. * Bindings are just events, so the whole update process is simply
  5752. * removing old events and adding new ones.
  5753. *
  5754. * @private
  5755. * @function Highcharts.NavigationBindings#update
  5756. */
  5757. update: function (options) {
  5758. this.options = merge(true, this.options, options);
  5759. this.removeEvents();
  5760. this.initEvents();
  5761. },
  5762. /**
  5763. * Remove all events created in the navigation.
  5764. *
  5765. * @private
  5766. * @function Highcharts.NavigationBindings#removeEvents
  5767. */
  5768. removeEvents: function () {
  5769. this.eventsToUnbind.forEach(function (unbinder) {
  5770. unbinder();
  5771. });
  5772. },
  5773. destroy: function () {
  5774. this.removeEvents();
  5775. },
  5776. /**
  5777. * General utils for bindings
  5778. *
  5779. * @private
  5780. * @name Highcharts.NavigationBindings#utils
  5781. * @type {bindingsUtils}
  5782. */
  5783. utils: bindingsUtils
  5784. });
  5785. H.Chart.prototype.initNavigationBindings = function () {
  5786. var chart = this,
  5787. options = chart.options;
  5788. if (options && options.navigation && options.navigation.bindings) {
  5789. chart.navigationBindings = new H.NavigationBindings(
  5790. chart,
  5791. options.navigation
  5792. );
  5793. chart.navigationBindings.initEvents();
  5794. chart.navigationBindings.initUpdate();
  5795. }
  5796. };
  5797. addEvent(H.Chart, 'load', function () {
  5798. this.initNavigationBindings();
  5799. });
  5800. addEvent(H.Chart, 'destroy', function () {
  5801. if (this.navigationBindings) {
  5802. this.navigationBindings.destroy();
  5803. }
  5804. });
  5805. addEvent(H.NavigationBindings, 'deselectButton', function () {
  5806. this.selectedButtonElement = null;
  5807. });
  5808. // Show edit-annotation form:
  5809. function selectableAnnotation(annotationType) {
  5810. var originalClick = annotationType.prototype.defaultOptions.events &&
  5811. annotationType.prototype.defaultOptions.events.click;
  5812. function selectAndshowPopup(event) {
  5813. var annotation = this,
  5814. navigation = annotation.chart.navigationBindings,
  5815. prevAnnotation = navigation.activeAnnotation;
  5816. if (originalClick) {
  5817. originalClick.click.call(annotation, event);
  5818. }
  5819. if (prevAnnotation !== annotation) {
  5820. // Select current:
  5821. navigation.deselectAnnotation();
  5822. navigation.activeAnnotation = annotation;
  5823. annotation.setControlPointsVisibility(true);
  5824. fireEvent(
  5825. navigation,
  5826. 'showPopup',
  5827. {
  5828. annotation: annotation,
  5829. formType: 'annotation-toolbar',
  5830. options: navigation.annotationToFields(annotation),
  5831. onSubmit: function (data) {
  5832. var config = {},
  5833. typeOptions;
  5834. if (data.actionType === 'remove') {
  5835. navigation.activeAnnotation = false;
  5836. navigation.chart.removeAnnotation(annotation);
  5837. } else {
  5838. navigation.fieldsToOptions(data.fields, config);
  5839. navigation.deselectAnnotation();
  5840. typeOptions = config.typeOptions;
  5841. if (annotation.options.type === 'measure') {
  5842. // Manually disable crooshars according to
  5843. // stroke width of the shape:
  5844. typeOptions.crosshairY.enabled =
  5845. typeOptions.crosshairY.strokeWidth !== 0;
  5846. typeOptions.crosshairX.enabled =
  5847. typeOptions.crosshairX.strokeWidth !== 0;
  5848. }
  5849. annotation.update(config);
  5850. }
  5851. }
  5852. }
  5853. );
  5854. } else {
  5855. // Deselect current:
  5856. navigation.deselectAnnotation();
  5857. fireEvent(navigation, 'closePopup');
  5858. }
  5859. // Let bubble event to chart.click:
  5860. event.activeAnnotation = true;
  5861. }
  5862. H.merge(
  5863. true,
  5864. annotationType.prototype.defaultOptions.events,
  5865. {
  5866. click: selectAndshowPopup
  5867. }
  5868. );
  5869. }
  5870. if (H.Annotation) {
  5871. // Basic shapes:
  5872. selectableAnnotation(H.Annotation);
  5873. // Advanced annotations:
  5874. H.objectEach(H.Annotation.types, function (annotationType) {
  5875. selectableAnnotation(annotationType);
  5876. });
  5877. }
  5878. H.setOptions({
  5879. /**
  5880. * @optionparent lang
  5881. */
  5882. lang: {
  5883. /**
  5884. * Configure the Popup strings in the chart. Requires the
  5885. * `annotations.js` or `annotations-advanced.src.js` module to be
  5886. * loaded.
  5887. *
  5888. * @since 7.0.0
  5889. * @type {Object}
  5890. * @product highcharts highstock
  5891. */
  5892. navigation: {
  5893. /**
  5894. * Translations for all field names used in popup.
  5895. *
  5896. * @product highcharts highstock
  5897. * @type {Object}
  5898. */
  5899. popup: {
  5900. simpleShapes: 'Simple shapes',
  5901. lines: 'Lines',
  5902. circle: 'Circle',
  5903. rectangle: 'Rectangle',
  5904. label: 'Label',
  5905. shapeOptions: 'Shape options',
  5906. typeOptions: 'Details',
  5907. fill: 'Fill',
  5908. format: 'Text',
  5909. strokeWidth: 'Line width',
  5910. stroke: 'Line color',
  5911. title: 'Title',
  5912. name: 'Name',
  5913. labelOptions: 'Label options',
  5914. labels: 'Labels',
  5915. backgroundColor: 'Background color',
  5916. backgroundColors: 'Background colors',
  5917. borderColor: 'Border color',
  5918. borderRadius: 'Border radius',
  5919. borderWidth: 'Border width',
  5920. style: 'Style',
  5921. padding: 'Padding',
  5922. fontSize: 'Font size',
  5923. color: 'Color',
  5924. height: 'Height',
  5925. shapes: 'Shape options'
  5926. }
  5927. }
  5928. },
  5929. /**
  5930. * @optionparent navigation
  5931. * @product highcharts highstock
  5932. */
  5933. navigation: {
  5934. /**
  5935. * A CSS class name where all bindings will be attached to. Multiple
  5936. * charts on the same page should have separate class names to prevent
  5937. * duplicating events.
  5938. *
  5939. * @since 7.0.0
  5940. * @type {string}
  5941. */
  5942. bindingsClassName: 'highcharts-bindings-wrapper',
  5943. /**
  5944. * Bindings definitions for custom HTML buttons. Each binding implements
  5945. * simple event-driven interface:
  5946. *
  5947. * - `className`: classname used to bind event to
  5948. *
  5949. * - `init`: initial event, fired on button click
  5950. *
  5951. * - `start`: fired on first click on a chart
  5952. *
  5953. * - `steps`: array of sequential events fired one after another on each
  5954. * of users clicks
  5955. *
  5956. * - `end`: last event to be called after last step event
  5957. *
  5958. * @type {Highcharts.Dictionary<Highcharts.StockToolsBindingsObject>|*}
  5959. * @sample stock/stocktools/stocktools-thresholds
  5960. * Custom bindings in Highstock
  5961. * @since 7.0.0
  5962. * @product highcharts highstock
  5963. */
  5964. bindings: {
  5965. /**
  5966. * A circle annotation bindings. Includes `start` and one event in
  5967. * `steps` array.
  5968. *
  5969. * @type {Highcharts.StockToolsBindingsObject}
  5970. * @default {"className": "highcharts-circle-annotation", "start": function() {}, "steps": [function() {}]}
  5971. */
  5972. circleAnnotation: {
  5973. /** @ignore */
  5974. className: 'highcharts-circle-annotation',
  5975. /** @ignore */
  5976. start: function (e) {
  5977. var x = this.chart.xAxis[0].toValue(e.chartX),
  5978. y = this.chart.yAxis[0].toValue(e.chartY),
  5979. annotation;
  5980. annotation = this.chart.addAnnotation({
  5981. langKey: 'circle',
  5982. shapes: [{
  5983. type: 'circle',
  5984. point: {
  5985. xAxis: 0,
  5986. yAxis: 0,
  5987. x: x,
  5988. y: y
  5989. },
  5990. r: 5,
  5991. controlPoints: [{
  5992. positioner: function (target) {
  5993. var xy = H.Annotation.MockPoint
  5994. .pointToPixels(
  5995. target.points[0]
  5996. ),
  5997. r = target.options.r;
  5998. return {
  5999. x: xy.x + r * Math.cos(Math.PI / 4) -
  6000. this.graphic.width / 2,
  6001. y: xy.y + r * Math.sin(Math.PI / 4) -
  6002. this.graphic.height / 2
  6003. };
  6004. },
  6005. events: {
  6006. // TRANSFORM RADIUS ACCORDING TO Y
  6007. // TRANSLATION
  6008. drag: function (e, target) {
  6009. var annotation = target.annotation,
  6010. position = this
  6011. .mouseMoveToTranslation(e);
  6012. target.setRadius(
  6013. Math.max(
  6014. target.options.r +
  6015. position.y /
  6016. Math.sin(Math.PI / 4),
  6017. 5
  6018. )
  6019. );
  6020. annotation.options.shapes[0] =
  6021. annotation.userOptions.shapes[0] =
  6022. target.options;
  6023. target.redraw(false);
  6024. }
  6025. }
  6026. }]
  6027. }]
  6028. });
  6029. return annotation;
  6030. },
  6031. /** @ignore */
  6032. steps: [
  6033. function (e, annotation) {
  6034. var point = annotation.options.shapes[0].point,
  6035. x = this.chart.xAxis[0].toPixels(point.x),
  6036. y = this.chart.yAxis[0].toPixels(point.y),
  6037. distance = Math.max(
  6038. Math.sqrt(
  6039. Math.pow(x - e.chartX, 2) +
  6040. Math.pow(y - e.chartY, 2)
  6041. ),
  6042. 5
  6043. );
  6044. annotation.update({
  6045. shapes: [{
  6046. r: distance
  6047. }]
  6048. });
  6049. }
  6050. ]
  6051. },
  6052. /**
  6053. * A rectangle annotation bindings. Includes `start` and one event
  6054. * in `steps` array.
  6055. *
  6056. * @type {Highcharts.StockToolsBindingsObject}
  6057. * @default {"className": "highcharts-rectangle-annotation", "start": function() {}, "steps": [function() {}]}
  6058. */
  6059. rectangleAnnotation: {
  6060. /** @ignore */
  6061. className: 'highcharts-rectangle-annotation',
  6062. /** @ignore */
  6063. start: function (e) {
  6064. var x = this.chart.xAxis[0].toValue(e.chartX),
  6065. y = this.chart.yAxis[0].toValue(e.chartY),
  6066. options = {
  6067. langKey: 'rectangle',
  6068. shapes: [{
  6069. type: 'rect',
  6070. point: {
  6071. x: x,
  6072. y: y,
  6073. xAxis: 0,
  6074. yAxis: 0
  6075. },
  6076. width: 5,
  6077. height: 5,
  6078. controlPoints: [{
  6079. positioner: function (target) {
  6080. var xy = H.Annotation.MockPoint
  6081. .pointToPixels(
  6082. target.points[0]
  6083. );
  6084. return {
  6085. x: xy.x + target.options.width - 4,
  6086. y: xy.y + target.options.height - 4
  6087. };
  6088. },
  6089. events: {
  6090. drag: function (e, target) {
  6091. var annotation = target.annotation,
  6092. xy = this
  6093. .mouseMoveToTranslation(e);
  6094. target.options.width = Math.max(
  6095. target.options.width + xy.x,
  6096. 5
  6097. );
  6098. target.options.height = Math.max(
  6099. target.options.height + xy.y,
  6100. 5
  6101. );
  6102. annotation.options.shapes[0] =
  6103. target.options;
  6104. annotation.userOptions.shapes[0] =
  6105. target.options;
  6106. target.redraw(false);
  6107. }
  6108. }
  6109. }]
  6110. }]
  6111. };
  6112. return this.chart.addAnnotation(options);
  6113. },
  6114. /** @ignore */
  6115. steps: [
  6116. function (e, annotation) {
  6117. var xAxis = this.chart.xAxis[0],
  6118. yAxis = this.chart.yAxis[0],
  6119. point = annotation.options.shapes[0].point,
  6120. x = xAxis.toPixels(point.x),
  6121. y = yAxis.toPixels(point.y),
  6122. width = Math.max(e.chartX - x, 5),
  6123. height = Math.max(e.chartY - y, 5);
  6124. annotation.update({
  6125. shapes: [{
  6126. width: width,
  6127. height: height,
  6128. point: {
  6129. x: point.x,
  6130. y: point.y
  6131. }
  6132. }]
  6133. });
  6134. }
  6135. ]
  6136. },
  6137. /**
  6138. * A label annotation bindings. Includes `start` event only.
  6139. *
  6140. * @type {Highcharts.StockToolsBindingsObject}
  6141. * @default {"className": "highcharts-label-annotation", "start": function() {}, "steps": [function() {}]}
  6142. */
  6143. labelAnnotation: {
  6144. /** @ignore */
  6145. className: 'highcharts-label-annotation',
  6146. /** @ignore */
  6147. start: function (e) {
  6148. var x = this.chart.xAxis[0].toValue(e.chartX),
  6149. y = this.chart.yAxis[0].toValue(e.chartY);
  6150. this.chart.addAnnotation({
  6151. langKey: 'label',
  6152. labelOptions: {
  6153. format: '{y:.2f}'
  6154. },
  6155. labels: [{
  6156. point: {
  6157. x: x,
  6158. y: y,
  6159. xAxis: 0,
  6160. yAxis: 0
  6161. },
  6162. controlPoints: [{
  6163. symbol: 'triangle-down',
  6164. positioner: function (target) {
  6165. if (!target.graphic.placed) {
  6166. return {
  6167. x: 0,
  6168. y: -9e7
  6169. };
  6170. }
  6171. var xy = H.Annotation.MockPoint
  6172. .pointToPixels(
  6173. target.points[0]
  6174. );
  6175. return {
  6176. x: xy.x - this.graphic.width / 2,
  6177. y: xy.y - this.graphic.height / 2
  6178. };
  6179. },
  6180. // TRANSLATE POINT/ANCHOR
  6181. events: {
  6182. drag: function (e, target) {
  6183. var xy = this.mouseMoveToTranslation(e);
  6184. target.translatePoint(xy.x, xy.y);
  6185. target.annotation.labels[0].options =
  6186. target.options;
  6187. target.redraw(false);
  6188. }
  6189. }
  6190. }, {
  6191. symbol: 'square',
  6192. positioner: function (target) {
  6193. if (!target.graphic.placed) {
  6194. return {
  6195. x: 0,
  6196. y: -9e7
  6197. };
  6198. }
  6199. return {
  6200. x: target.graphic.alignAttr.x -
  6201. this.graphic.width / 2,
  6202. y: target.graphic.alignAttr.y -
  6203. this.graphic.height / 2
  6204. };
  6205. },
  6206. // TRANSLATE POSITION WITHOUT CHANGING THE
  6207. // ANCHOR
  6208. events: {
  6209. drag: function (e, target) {
  6210. var xy = this.mouseMoveToTranslation(e);
  6211. target.translate(xy.x, xy.y);
  6212. target.annotation.labels[0].options =
  6213. target.options;
  6214. target.redraw(false);
  6215. }
  6216. }
  6217. }],
  6218. overflow: 'none',
  6219. crop: true
  6220. }]
  6221. });
  6222. }
  6223. }
  6224. },
  6225. /**
  6226. * A `showPopup` event. Fired when selecting for example an annotation.
  6227. *
  6228. * @type {Function}
  6229. * @apioption navigation.events.showPopup
  6230. */
  6231. /**
  6232. * A `hidePopop` event. Fired when Popup should be hidden, for exampole
  6233. * when clicking on an annotation again.
  6234. *
  6235. * @type {Function}
  6236. * @apioption navigation.events.hidePopup
  6237. */
  6238. /**
  6239. * Event fired on a button click.
  6240. *
  6241. * @type {Function}
  6242. * @sample highcharts/annotations/gui/
  6243. * Change icon in a dropddown on event
  6244. * @sample highcharts/annotations/gui-buttons/
  6245. * Change button class on event
  6246. * @apioption navigation.events.selectButton
  6247. */
  6248. /**
  6249. * Event fired when button state should change, for example after
  6250. * adding an annotation.
  6251. *
  6252. * @type {Function}
  6253. * @sample highcharts/annotations/gui/
  6254. * Change icon in a dropddown on event
  6255. * @sample highcharts/annotations/gui-buttons/
  6256. * Change button class on event
  6257. * @apioption navigation.events.deselectButton
  6258. */
  6259. /**
  6260. * Events to communicate between Stock Tools and custom GUI.
  6261. *
  6262. * @since 7.0.0
  6263. * @product highcharts highstock
  6264. * @optionparent navigation.events
  6265. */
  6266. events: {}
  6267. }
  6268. });
  6269. }(Highcharts, chartNavigation));
  6270. (function (H) {
  6271. /**
  6272. * Popup generator for Stock tools
  6273. *
  6274. * (c) 2009-2017 Sebastian Bochan
  6275. *
  6276. * License: www.highcharts.com/license
  6277. */
  6278. var addEvent = H.addEvent,
  6279. createElement = H.createElement,
  6280. objectEach = H.objectEach,
  6281. pick = H.pick,
  6282. wrap = H.wrap,
  6283. isString = H.isString,
  6284. isObject = H.isObject,
  6285. isArray = H.isArray,
  6286. indexFilter = /\d/g,
  6287. PREFIX = 'highcharts-',
  6288. DIV = 'div',
  6289. INPUT = 'input',
  6290. LABEL = 'label',
  6291. BUTTON = 'button',
  6292. SELECT = 'select',
  6293. OPTION = 'option',
  6294. SPAN = 'span',
  6295. UL = 'ul',
  6296. LI = 'li',
  6297. H3 = 'h3';
  6298. // onContainerMouseDown blocks internal popup events, due to e.preventDefault.
  6299. // Related issue #4606
  6300. wrap(H.Pointer.prototype, 'onContainerMouseDown', function (proceed, e) {
  6301. var popupClass = e.target && e.target.className;
  6302. // elements is not in popup
  6303. if (!(isString(popupClass) &&
  6304. popupClass.indexOf(PREFIX + 'popup-field') >= 0)
  6305. ) {
  6306. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  6307. }
  6308. });
  6309. H.Popup = function (parentDiv) {
  6310. this.init(parentDiv);
  6311. };
  6312. H.Popup.prototype = {
  6313. /*
  6314. * Initialize the popup. Create base div and add close button.
  6315. *
  6316. * @param {HTMLDOMElement} - container where popup should be placed
  6317. *
  6318. * @return {HTMLDOMElement} - return created popup's div
  6319. *
  6320. */
  6321. init: function (parentDiv) {
  6322. // create popup div
  6323. this.container = createElement(DIV, {
  6324. className: PREFIX + 'popup'
  6325. }, null, parentDiv);
  6326. this.lang = this.getLangpack();
  6327. // add close button
  6328. this.addCloseBtn();
  6329. },
  6330. /*
  6331. * Create HTML element and attach click event (close popup).
  6332. *
  6333. */
  6334. addCloseBtn: function () {
  6335. var _self = this,
  6336. closeBtn;
  6337. // create close popup btn
  6338. closeBtn = createElement(DIV, {
  6339. className: PREFIX + 'popup-close'
  6340. }, null, this.container);
  6341. ['click', 'touchstart'].forEach(function (eventName) {
  6342. addEvent(closeBtn, eventName, function () {
  6343. _self.closePopup();
  6344. });
  6345. });
  6346. },
  6347. /*
  6348. * Create two columns (divs) in HTML.
  6349. *
  6350. * @param {HTMLDOMElement} - container of columns
  6351. *
  6352. * @return {Object} - reference to two HTML columns
  6353. *
  6354. */
  6355. addColsContainer: function (container) {
  6356. var rhsCol,
  6357. lhsCol;
  6358. // left column
  6359. lhsCol = createElement(DIV, {
  6360. className: PREFIX + 'popup-lhs-col'
  6361. }, null, container);
  6362. // right column
  6363. rhsCol = createElement(DIV, {
  6364. className: PREFIX + 'popup-rhs-col'
  6365. }, null, container);
  6366. // wrapper content
  6367. createElement(DIV, {
  6368. className: PREFIX + 'popup-rhs-col-wrapper'
  6369. }, null, rhsCol);
  6370. return {
  6371. lhsCol: lhsCol,
  6372. rhsCol: rhsCol
  6373. };
  6374. },
  6375. /*
  6376. * Create input with label.
  6377. *
  6378. * @param {String} - chain of fields i.e params.styles.fontSize
  6379. * @param {String} - indicator type
  6380. * @param {HTMLDOMElement} - container where elements should be added
  6381. * @param {String} - dafault value of input i.e period value is 14,
  6382. * extracted from defaultOptions (ADD mode) or series options (EDIT mode)
  6383. *
  6384. */
  6385. addInput: function (option, type, parentDiv, value) {
  6386. var optionParamList = option.split('.'),
  6387. optionName = optionParamList[optionParamList.length - 1],
  6388. lang = this.lang,
  6389. inputName = PREFIX + type + '-' + optionName;
  6390. if (!inputName.match(indexFilter)) {
  6391. // add label
  6392. createElement(
  6393. LABEL, {
  6394. innerHTML: lang[optionName] || optionName,
  6395. htmlFor: inputName
  6396. },
  6397. null,
  6398. parentDiv
  6399. );
  6400. }
  6401. // add input
  6402. createElement(
  6403. INPUT,
  6404. {
  6405. name: inputName,
  6406. value: value[0],
  6407. type: value[1],
  6408. className: PREFIX + 'popup-field'
  6409. },
  6410. null,
  6411. parentDiv
  6412. ).setAttribute(PREFIX + 'data-name', option);
  6413. },
  6414. /*
  6415. * Create button.
  6416. *
  6417. * @param {HTMLDOMElement} - container where elements should be added
  6418. * @param {String} - text placed as button label
  6419. * @param {String} - add | edit | remove
  6420. * @param {Function} - on click callback
  6421. * @param {HTMLDOMElement} - container where inputs are generated
  6422. *
  6423. * @return {HTMLDOMElement} - html button
  6424. */
  6425. addButton: function (parentDiv, label, type, callback, fieldsDiv) {
  6426. var _self = this,
  6427. closePopup = this.closePopup,
  6428. getFields = this.getFields,
  6429. button;
  6430. button = createElement(BUTTON, {
  6431. innerHTML: label
  6432. }, null, parentDiv);
  6433. ['click', 'touchstart'].forEach(function (eventName) {
  6434. addEvent(button, eventName, function () {
  6435. closePopup.call(_self);
  6436. return callback(
  6437. getFields(fieldsDiv, type)
  6438. );
  6439. });
  6440. });
  6441. return button;
  6442. },
  6443. /*
  6444. * Get values from all inputs and create JSON.
  6445. *
  6446. * @param {HTMLDOMElement} - container where inputs are created
  6447. * @param {String} - add | edit | remove
  6448. *
  6449. * @return {Object} - fields
  6450. */
  6451. getFields: function (parentDiv, type) {
  6452. var inputList = parentDiv.querySelectorAll('input'),
  6453. optionSeries = '#' + PREFIX + 'select-series > option:checked',
  6454. optionVolume = '#' + PREFIX + 'select-volume > option:checked',
  6455. linkedTo = parentDiv.querySelectorAll(optionSeries)[0],
  6456. volumeTo = parentDiv.querySelectorAll(optionVolume)[0],
  6457. seriesId,
  6458. param,
  6459. fieldsOutput;
  6460. fieldsOutput = {
  6461. actionType: type,
  6462. linkedTo: linkedTo && linkedTo.getAttribute('value'),
  6463. fields: { }
  6464. };
  6465. inputList.forEach(function (input) {
  6466. param = input.getAttribute(PREFIX + 'data-name');
  6467. seriesId = input.getAttribute(PREFIX + 'data-series-id');
  6468. // params
  6469. if (seriesId) {
  6470. fieldsOutput.seriesId = input.value;
  6471. } else if (param) {
  6472. fieldsOutput.fields[param] = input.value;
  6473. } else {
  6474. // type like sma / ema
  6475. fieldsOutput.type = input.value;
  6476. }
  6477. });
  6478. if (volumeTo) {
  6479. fieldsOutput.fields['params.volumeSeriesID'] = volumeTo
  6480. .getAttribute('value');
  6481. }
  6482. return fieldsOutput;
  6483. },
  6484. /*
  6485. * Reset content of the current popup and show.
  6486. *
  6487. * @param {Chart} - chart
  6488. * @param {Function} - on click callback
  6489. *
  6490. * @return {Object} - fields
  6491. */
  6492. showPopup: function () {
  6493. var popupDiv = this.container,
  6494. toolbarClass = PREFIX + 'annotation-toolbar',
  6495. popupCloseBtn = popupDiv
  6496. .querySelectorAll('.' + PREFIX + 'popup-close')[0];
  6497. // reset content
  6498. popupDiv.innerHTML = '';
  6499. // reset toolbar styles if exists
  6500. if (popupDiv.className.indexOf(toolbarClass) >= 0) {
  6501. popupDiv.classList.remove(toolbarClass);
  6502. // reset toolbar inline styles
  6503. popupDiv.removeAttribute('style');
  6504. }
  6505. // add close button
  6506. popupDiv.appendChild(popupCloseBtn);
  6507. popupDiv.style.display = 'block';
  6508. },
  6509. /*
  6510. * Hide popup.
  6511. *
  6512. */
  6513. closePopup: function () {
  6514. this.popup.container.style.display = 'none';
  6515. },
  6516. /*
  6517. * Create content and show popup.
  6518. *
  6519. * @param {String} - type of popup i.e indicators
  6520. * @param {Chart} - chart
  6521. * @param {Object} - options
  6522. * @param {Function} - on click callback
  6523. *
  6524. */
  6525. showForm: function (type, chart, options, callback) {
  6526. this.popup = chart.navigationBindings.popup;
  6527. // show blank popup
  6528. this.showPopup();
  6529. // indicator form
  6530. if (type === 'indicators') {
  6531. this.indicators.addForm.call(this, chart, options, callback);
  6532. }
  6533. // annotation small toolbar
  6534. if (type === 'annotation-toolbar') {
  6535. this.annotations.addToolbar.call(this, chart, options, callback);
  6536. }
  6537. // annotation edit form
  6538. if (type === 'annotation-edit') {
  6539. this.annotations.addForm.call(this, chart, options, callback);
  6540. }
  6541. // flags form - add / edit
  6542. if (type === 'flag') {
  6543. this.annotations.addForm.call(this, chart, options, callback, true);
  6544. }
  6545. },
  6546. /*
  6547. * Return lang definitions for popup.
  6548. *
  6549. * @return {Object} - elements translations.
  6550. */
  6551. getLangpack: function () {
  6552. return H.getOptions().lang.navigation.popup;
  6553. },
  6554. annotations: {
  6555. /*
  6556. * Create annotation simple form. It contains two buttons
  6557. * (edit / remove) and text label.
  6558. *
  6559. * @param {Chart} - chart
  6560. * @param {Object} - options
  6561. * @param {Function} - on click callback
  6562. *
  6563. */
  6564. addToolbar: function (chart, options, callback) {
  6565. var _self = this,
  6566. lang = this.lang,
  6567. popupDiv = this.popup.container,
  6568. showForm = this.showForm,
  6569. toolbarClass = PREFIX + 'annotation-toolbar',
  6570. button;
  6571. // set small size
  6572. if (popupDiv.className.indexOf(toolbarClass) === -1) {
  6573. popupDiv.className += ' ' + toolbarClass;
  6574. }
  6575. // set position
  6576. popupDiv.style.top = chart.plotTop + 10 + 'px';
  6577. // create label
  6578. createElement(SPAN, {
  6579. innerHTML: pick(
  6580. // Advanced annotations:
  6581. lang[options.langKey] || options.langKey,
  6582. // Basic shapes:
  6583. options.shapes && options.shapes[0].type
  6584. )
  6585. }, null, popupDiv);
  6586. // add buttons
  6587. button = this.addButton(
  6588. popupDiv,
  6589. lang.removeButton || 'remove',
  6590. 'remove',
  6591. callback,
  6592. popupDiv
  6593. );
  6594. button.className += ' ' + PREFIX + 'annotation-remove-button';
  6595. button = this.addButton(
  6596. popupDiv,
  6597. lang.editButton || 'edit',
  6598. 'edit',
  6599. function () {
  6600. showForm.call(
  6601. _self,
  6602. 'annotation-edit',
  6603. chart,
  6604. options,
  6605. callback
  6606. );
  6607. },
  6608. popupDiv
  6609. );
  6610. button.className += ' ' + PREFIX + 'annotation-edit-button';
  6611. },
  6612. /*
  6613. * Create annotation simple form.
  6614. * It contains fields with param names.
  6615. *
  6616. * @param {Chart} - chart
  6617. * @param {Object} - options
  6618. * @param {Function} - on click callback
  6619. * @param {Boolean} - if it is a form declared for init annotation
  6620. *
  6621. */
  6622. addForm: function (chart, options, callback, isInit) {
  6623. var popupDiv = this.popup.container,
  6624. lang = this.lang,
  6625. bottomRow,
  6626. lhsCol;
  6627. // create title of annotations
  6628. lhsCol = createElement('h2', {
  6629. innerHTML: lang[options.langKey] || options.langKey,
  6630. className: PREFIX + 'popup-main-title'
  6631. }, null, popupDiv);
  6632. // left column
  6633. lhsCol = createElement(DIV, {
  6634. className: PREFIX + 'popup-lhs-col ' + PREFIX + 'popup-lhs-full'
  6635. }, null, popupDiv);
  6636. bottomRow = createElement(DIV, {
  6637. className: PREFIX + 'popup-bottom-row'
  6638. }, null, popupDiv);
  6639. this.annotations.addFormFields.call(
  6640. this,
  6641. lhsCol,
  6642. chart,
  6643. '',
  6644. options,
  6645. [],
  6646. true
  6647. );
  6648. this.addButton(
  6649. bottomRow,
  6650. isInit ?
  6651. (lang.addButton || 'add') :
  6652. (lang.saveButton || 'save'),
  6653. isInit ? 'add' : 'save',
  6654. callback,
  6655. popupDiv
  6656. );
  6657. },
  6658. /*
  6659. * Create annotation's form fields.
  6660. *
  6661. * @param {HTMLDOMElement} - div where inputs are placed
  6662. * @param {Chart} - chart
  6663. * @param {String} - name of parent to create chain of names
  6664. * @param {Object} - options
  6665. * @param {Array} - storage - array where all items are stored
  6666. * @param {Boolean} - isRoot - recursive flag for root
  6667. *
  6668. */
  6669. addFormFields: function (
  6670. parentDiv,
  6671. chart,
  6672. parentNode,
  6673. options,
  6674. storage,
  6675. isRoot
  6676. ) {
  6677. var _self = this,
  6678. addFormFields = this.annotations.addFormFields,
  6679. addInput = this.addInput,
  6680. lang = this.lang,
  6681. parentFullName,
  6682. titleName;
  6683. objectEach(options, function (value, option) {
  6684. // create name like params.styles.fontSize
  6685. parentFullName = parentNode !== '' ?
  6686. parentNode + '.' + option : option;
  6687. if (isObject(value)) {
  6688. if (
  6689. // value is object of options
  6690. !isArray(value) ||
  6691. // array of objects with params. i.e labels in Fibonacci
  6692. (isArray(value) && isObject(value[0]))
  6693. ) {
  6694. titleName = lang[option] || option;
  6695. if (!titleName.match(indexFilter)) {
  6696. storage.push([
  6697. true,
  6698. titleName,
  6699. parentDiv
  6700. ]);
  6701. }
  6702. addFormFields.call(
  6703. _self,
  6704. parentDiv,
  6705. chart,
  6706. parentFullName,
  6707. value,
  6708. storage,
  6709. false
  6710. );
  6711. } else {
  6712. storage.push([
  6713. _self,
  6714. parentFullName,
  6715. 'annotation',
  6716. parentDiv,
  6717. value
  6718. ]);
  6719. }
  6720. }
  6721. });
  6722. if (isRoot) {
  6723. storage = storage.sort(function (a) {
  6724. return a[1].match(/format/g) ? -1 : 1;
  6725. });
  6726. storage.forEach(function (genInput) {
  6727. if (genInput[0] === true) {
  6728. createElement(SPAN, {
  6729. className: PREFIX + 'annotation-title',
  6730. innerHTML: genInput[1]
  6731. }, null, genInput[2]);
  6732. } else {
  6733. addInput.apply(genInput[0], genInput.splice(1));
  6734. }
  6735. });
  6736. }
  6737. }
  6738. },
  6739. indicators: {
  6740. /*
  6741. * Create indicator's form. It contains two tabs (ADD and EDIT) with
  6742. * content.
  6743. *
  6744. * @param {Chart} - chart
  6745. * @param {Object} - options
  6746. * @param {Function} - on click callback
  6747. *
  6748. */
  6749. addForm: function (chart, options, callback) {
  6750. var tabsContainers,
  6751. indicators = this.indicators,
  6752. lang = this.lang,
  6753. buttonParentDiv;
  6754. // add tabs
  6755. this.tabs.init.call(this, chart);
  6756. // get all tabs content divs
  6757. tabsContainers = this.popup.container
  6758. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  6759. // ADD tab
  6760. this.addColsContainer(tabsContainers[0]);
  6761. indicators.addIndicatorList.call(
  6762. this,
  6763. chart,
  6764. tabsContainers[0],
  6765. 'add'
  6766. );
  6767. buttonParentDiv = tabsContainers[0]
  6768. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  6769. this.addButton(
  6770. buttonParentDiv,
  6771. lang.addButton || 'add',
  6772. 'add',
  6773. callback,
  6774. buttonParentDiv
  6775. );
  6776. // EDIT tab
  6777. this.addColsContainer(tabsContainers[1]);
  6778. indicators.addIndicatorList.call(
  6779. this,
  6780. chart,
  6781. tabsContainers[1],
  6782. 'edit'
  6783. );
  6784. buttonParentDiv = tabsContainers[1]
  6785. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  6786. this.addButton(
  6787. buttonParentDiv,
  6788. lang.saveButton || 'save',
  6789. 'edit',
  6790. callback,
  6791. buttonParentDiv
  6792. );
  6793. this.addButton(
  6794. buttonParentDiv,
  6795. lang.removeButton || 'remove',
  6796. 'remove',
  6797. callback,
  6798. buttonParentDiv
  6799. );
  6800. },
  6801. /*
  6802. * Create HTML list of all indicators (ADD mode) or added indicators
  6803. * (EDIT mode).
  6804. *
  6805. * @param {Chart} - chart
  6806. * @param {HTMLDOMElement} - container where list is added
  6807. * @param {String} - 'edit' or 'add' mode
  6808. *
  6809. */
  6810. addIndicatorList: function (chart, parentDiv, listType) {
  6811. var _self = this,
  6812. lhsCol = parentDiv
  6813. .querySelectorAll('.' + PREFIX + 'popup-lhs-col')[0],
  6814. rhsCol = parentDiv
  6815. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0],
  6816. defaultOptions = H.getOptions(),
  6817. isEdit = listType === 'edit',
  6818. series = isEdit ? chart.series : // EDIT mode
  6819. defaultOptions.plotOptions, // ADD mode
  6820. addFormFields = this.indicators.addFormFields,
  6821. rhsColWrapper,
  6822. indicatorList,
  6823. item;
  6824. // create wrapper for list
  6825. indicatorList = createElement(UL, {
  6826. className: PREFIX + 'indicator-list'
  6827. }, null, lhsCol);
  6828. rhsColWrapper = rhsCol
  6829. .querySelectorAll('.' + PREFIX + 'popup-rhs-col-wrapper')[0];
  6830. objectEach(series, function (serie, value) {
  6831. var seriesOptions = serie.options;
  6832. if (
  6833. serie.params ||
  6834. seriesOptions && seriesOptions.params
  6835. ) {
  6836. var indicatorNameType = _self.indicators
  6837. .getNameType(serie, value),
  6838. indicatorType = indicatorNameType.type;
  6839. item = createElement(LI, {
  6840. className: PREFIX + 'indicator-list',
  6841. innerHTML: indicatorNameType.name
  6842. }, null, indicatorList);
  6843. ['click', 'touchstart'].forEach(function (eventName) {
  6844. addEvent(item, eventName, function () {
  6845. addFormFields.call(
  6846. _self,
  6847. chart,
  6848. isEdit ? serie : series[indicatorType],
  6849. indicatorNameType.type,
  6850. rhsColWrapper
  6851. );
  6852. // add hidden input with series.id
  6853. if (isEdit && serie.options) {
  6854. createElement(INPUT, {
  6855. type: 'hidden',
  6856. name: PREFIX + 'id-' + indicatorType,
  6857. value: serie.options.id
  6858. }, null, rhsColWrapper)
  6859. .setAttribute(
  6860. PREFIX + 'data-series-id',
  6861. serie.options.id
  6862. );
  6863. }
  6864. });
  6865. });
  6866. }
  6867. });
  6868. // select first item from the list
  6869. if (indicatorList.childNodes.length > 0) {
  6870. indicatorList.childNodes[0].click();
  6871. }
  6872. },
  6873. /*
  6874. * Extract full name and type of requested indicator.
  6875. *
  6876. * @param {Series} - series which name is needed.
  6877. * (EDIT mode - defaultOptions.series, ADD mode - indicator series).
  6878. * @param {String} - indicator type like: sma, ema, etc.
  6879. *
  6880. * @return {Object} - series name and type like: sma, ema, etc.
  6881. *
  6882. */
  6883. getNameType: function (series, type) {
  6884. var options = series.options,
  6885. seriesTypes = H.seriesTypes,
  6886. // add mode
  6887. seriesName = seriesTypes[type] &&
  6888. seriesTypes[type].prototype.nameBase || type.toUpperCase(),
  6889. seriesType = type;
  6890. // edit
  6891. if (options && options.type) {
  6892. seriesType = series.options.type;
  6893. seriesName = series.name;
  6894. }
  6895. return {
  6896. name: seriesName,
  6897. type: seriesType
  6898. };
  6899. },
  6900. /*
  6901. * List all series with unique ID. Its mandatory for indicators to set
  6902. * correct linking.
  6903. *
  6904. * @param {String} - indicator type like: sma, ema, etc.
  6905. * @param {String} - type of select i.e series or volume.
  6906. * @param {Chart} - chart
  6907. * @param {HTMLDOMElement} - element where created HTML list is added
  6908. *
  6909. */
  6910. listAllSeries: function (type, optionName, chart, parentDiv) {
  6911. var selectName = PREFIX + optionName + '-type-' + type,
  6912. lang = this.lang,
  6913. selectBox,
  6914. seriesOptions;
  6915. createElement(
  6916. LABEL, {
  6917. innerHTML: lang[optionName] || optionName,
  6918. htmlFor: selectName
  6919. },
  6920. null,
  6921. parentDiv
  6922. );
  6923. // select type
  6924. selectBox = createElement(
  6925. SELECT,
  6926. {
  6927. name: selectName,
  6928. className: PREFIX + 'popup-field'
  6929. },
  6930. null,
  6931. parentDiv
  6932. );
  6933. selectBox.setAttribute('id', PREFIX + 'select-' + optionName);
  6934. // list all series which have id - mandatory for creating indicator
  6935. chart.series.forEach(function (serie) {
  6936. seriesOptions = serie.options;
  6937. if (
  6938. !seriesOptions.params &&
  6939. seriesOptions.id &&
  6940. seriesOptions.id !== PREFIX + 'navigator-series'
  6941. ) {
  6942. createElement(
  6943. OPTION,
  6944. {
  6945. innerHTML: seriesOptions.name || seriesOptions.id,
  6946. value: seriesOptions.id
  6947. },
  6948. null,
  6949. selectBox
  6950. );
  6951. }
  6952. });
  6953. },
  6954. /*
  6955. * Create typical inputs for chosen indicator. Fields are extracted from
  6956. * defaultOptions (ADD mode) or current indicator (ADD mode). Two extra
  6957. * fields are added:
  6958. * - hidden input - contains indicator type (required for callback)
  6959. * - select - list of series which can be linked with indicator
  6960. *
  6961. * @param {Chart} - chart
  6962. * @param {Series} - indicator
  6963. * @param {String} - indicator type like: sma, ema, etc.
  6964. * @param {HTMLDOMElement} - element where created HTML list is added
  6965. *
  6966. */
  6967. addFormFields: function (chart, series, seriesType, rhsColWrapper) {
  6968. var fields = series.params || series.options.params,
  6969. getNameType = this.indicators.getNameType;
  6970. // reset current content
  6971. rhsColWrapper.innerHTML = '';
  6972. // create title (indicator name in the right column)
  6973. createElement(
  6974. H3,
  6975. {
  6976. className: PREFIX + 'indicator-title',
  6977. innerHTML: getNameType(series, seriesType).name
  6978. },
  6979. null,
  6980. rhsColWrapper
  6981. );
  6982. // input type
  6983. createElement(
  6984. INPUT,
  6985. {
  6986. type: 'hidden',
  6987. name: PREFIX + 'type-' + seriesType,
  6988. value: seriesType
  6989. },
  6990. null,
  6991. rhsColWrapper
  6992. );
  6993. // list all series with id
  6994. this.indicators.listAllSeries.call(
  6995. this,
  6996. seriesType,
  6997. 'series',
  6998. chart,
  6999. rhsColWrapper
  7000. );
  7001. if (fields.volumeSeriesID) {
  7002. this.indicators.listAllSeries.call(
  7003. this,
  7004. seriesType,
  7005. 'volume',
  7006. chart,
  7007. rhsColWrapper
  7008. );
  7009. }
  7010. // add param fields
  7011. this.indicators.addParamInputs.call(
  7012. this,
  7013. chart,
  7014. 'params',
  7015. fields,
  7016. seriesType,
  7017. rhsColWrapper
  7018. );
  7019. },
  7020. /*
  7021. * Recurent function which lists all fields, from params object and
  7022. * create them as inputs. Each input has unique `data-name` attribute,
  7023. * which keeps chain of fields i.e params.styles.fontSize.
  7024. *
  7025. * @param {Chart} - chart
  7026. * @param {String} - name of parent to create chain of names
  7027. * @param {Series} - fields - params which are based for input create
  7028. * @param {String} - indicator type like: sma, ema, etc.
  7029. * @param {HTMLDOMElement} - element where created HTML list is added
  7030. *
  7031. */
  7032. addParamInputs: function (chart, parentNode, fields, type, parentDiv) {
  7033. var _self = this,
  7034. addParamInputs = this.indicators.addParamInputs,
  7035. addInput = this.addInput,
  7036. parentFullName;
  7037. objectEach(fields, function (value, fieldName) {
  7038. // create name like params.styles.fontSize
  7039. parentFullName = parentNode + '.' + fieldName;
  7040. if (isObject(value)) {
  7041. addParamInputs.call(
  7042. _self,
  7043. chart,
  7044. parentFullName,
  7045. value,
  7046. type,
  7047. parentDiv
  7048. );
  7049. } else if (
  7050. // skip volume field which is created by addFormFields
  7051. parentFullName !== 'params.volumeSeriesID'
  7052. ) {
  7053. addInput.call(
  7054. _self,
  7055. parentFullName,
  7056. type,
  7057. parentDiv,
  7058. [value, 'text'] // all inputs are text type
  7059. );
  7060. }
  7061. });
  7062. },
  7063. /*
  7064. * Get amount of indicators added to chart.
  7065. *
  7066. * @return {Number} - Amount of indicators
  7067. */
  7068. getAmount: function () {
  7069. var series = this.series,
  7070. counter = 0;
  7071. objectEach(series, function (serie) {
  7072. var seriesOptions = serie.options;
  7073. if (
  7074. serie.params ||
  7075. seriesOptions && seriesOptions.params
  7076. ) {
  7077. counter++;
  7078. }
  7079. });
  7080. return counter;
  7081. }
  7082. },
  7083. tabs: {
  7084. /*
  7085. * Init tabs. Create tab menu items, tabs containers
  7086. *
  7087. * @param {Chart} - reference to current chart
  7088. *
  7089. */
  7090. init: function (chart) {
  7091. var tabs = this.tabs,
  7092. indicatorsCount = this.indicators.getAmount.call(chart),
  7093. firstTab; // run by default
  7094. // create menu items
  7095. firstTab = tabs.addMenuItem.call(this, 'add');
  7096. tabs.addMenuItem.call(this, 'edit', indicatorsCount);
  7097. // create tabs containers
  7098. tabs.addContentItem.call(this, 'add');
  7099. tabs.addContentItem.call(this, 'edit');
  7100. tabs.switchTabs.call(this, indicatorsCount);
  7101. // activate first tab
  7102. tabs.selectTab.call(this, firstTab, 0);
  7103. },
  7104. /*
  7105. * Create tab menu item
  7106. *
  7107. * @param {String} - `add` or `edit`
  7108. * @param {Number} - Disable tab when 0
  7109. *
  7110. * @return {HTMLDOMElement} - created HTML tab-menu element
  7111. */
  7112. addMenuItem: function (tabName, disableTab) {
  7113. var popupDiv = this.popup.container,
  7114. className = PREFIX + 'tab-item',
  7115. lang = this.lang,
  7116. menuItem;
  7117. if (disableTab === 0) {
  7118. className += ' ' + PREFIX + 'tab-disabled';
  7119. }
  7120. // tab 1
  7121. menuItem = createElement(
  7122. SPAN,
  7123. {
  7124. innerHTML: lang[tabName + 'Button'] || tabName,
  7125. className: className
  7126. },
  7127. null,
  7128. popupDiv
  7129. );
  7130. menuItem.setAttribute(PREFIX + 'data-tab-type', tabName);
  7131. return menuItem;
  7132. },
  7133. /*
  7134. * Create tab content
  7135. *
  7136. * @return {HTMLDOMElement} - created HTML tab-content element
  7137. *
  7138. */
  7139. addContentItem: function () {
  7140. var popupDiv = this.popup.container;
  7141. return createElement(
  7142. DIV,
  7143. {
  7144. className: PREFIX + 'tab-item-content'
  7145. },
  7146. null,
  7147. popupDiv
  7148. );
  7149. },
  7150. /*
  7151. * Add click event to each tab
  7152. *
  7153. * @param {Number} - Disable tab when 0
  7154. *
  7155. */
  7156. switchTabs: function (disableTab) {
  7157. var _self = this,
  7158. popupDiv = this.popup.container,
  7159. tabs = popupDiv.querySelectorAll('.' + PREFIX + 'tab-item'),
  7160. dataParam;
  7161. tabs.forEach(function (tab, i) {
  7162. dataParam = tab.getAttribute(PREFIX + 'data-tab-type');
  7163. if (dataParam === 'edit' && disableTab === 0) {
  7164. return;
  7165. }
  7166. ['click', 'touchstart'].forEach(function (eventName) {
  7167. addEvent(tab, eventName, function () {
  7168. // reset class on other elements
  7169. _self.tabs.deselectAll.call(_self);
  7170. _self.tabs.selectTab.call(_self, this, i);
  7171. });
  7172. });
  7173. });
  7174. },
  7175. /*
  7176. * Set tab as visible
  7177. *
  7178. * @param {HTMLDOMElement} - current tab
  7179. * @param {Number} - Index of tab in menu
  7180. *
  7181. */
  7182. selectTab: function (tab, index) {
  7183. var allTabs = this.popup.container
  7184. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  7185. tab.className += ' ' + PREFIX + 'tab-item-active';
  7186. allTabs[index].className += ' ' + PREFIX + 'tab-item-show';
  7187. },
  7188. /*
  7189. * Set all tabs as invisible.
  7190. *
  7191. */
  7192. deselectAll: function () {
  7193. var popupDiv = this.popup.container,
  7194. tabs = popupDiv
  7195. .querySelectorAll('.' + PREFIX + 'tab-item'),
  7196. tabsContent = popupDiv
  7197. .querySelectorAll('.' + PREFIX + 'tab-item-content'),
  7198. i;
  7199. for (i = 0; i < tabs.length; i++) {
  7200. tabs[i].classList.remove(PREFIX + 'tab-item-active');
  7201. tabsContent[i].classList.remove(PREFIX + 'tab-item-show');
  7202. }
  7203. }
  7204. }
  7205. };
  7206. addEvent(H.NavigationBindings, 'showPopup', function (config) {
  7207. if (!this.popup) {
  7208. // Add popup to main container
  7209. this.popup = new H.Popup(this.chart.container);
  7210. }
  7211. this.popup.showForm(
  7212. config.formType,
  7213. this.chart,
  7214. config.options,
  7215. config.onSubmit
  7216. );
  7217. });
  7218. addEvent(H.NavigationBindings, 'closePopup', function () {
  7219. if (this.popup) {
  7220. this.popup.closePopup();
  7221. }
  7222. });
  7223. }(Highcharts));
  7224. return (function () {
  7225. }());
  7226. }));