annotations.src.js 189 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001
  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 chartNavigation = (function () {
  3070. /**
  3071. * (c) 2010-2018 Paweł Fus
  3072. *
  3073. * License: www.highcharts.com/license
  3074. */
  3075. var chartNavigation = {
  3076. /**
  3077. * Initializes `chart.navigation` object which delegates `update()` methods
  3078. * to all other common classes (used in exporting and navigationBindings).
  3079. *
  3080. * @private
  3081. *
  3082. * @param {Highcharts.Chart} chart
  3083. * The chart instance.
  3084. */
  3085. initUpdate: function (chart) {
  3086. if (!chart.navigation) {
  3087. chart.navigation = {
  3088. updates: [],
  3089. update: function (options, redraw) {
  3090. this.updates.forEach(function (updateConfig) {
  3091. updateConfig.update.call(
  3092. updateConfig.context,
  3093. options,
  3094. redraw
  3095. );
  3096. });
  3097. }
  3098. };
  3099. }
  3100. },
  3101. /**
  3102. * Registers an `update()` method in the `chart.navigation` object.
  3103. *
  3104. * @private
  3105. *
  3106. * @param {function} update
  3107. * The `update()` method that will be called in `chart.update()`.
  3108. *
  3109. * @param {Highcharts.Chart} chart
  3110. * The chart instance. `update()` will use that as a context
  3111. * (`this`).
  3112. */
  3113. addUpdate: function (update, chart) {
  3114. if (!chart.navigation) {
  3115. this.initUpdate(chart);
  3116. }
  3117. chart.navigation.updates.push({
  3118. update: update,
  3119. context: chart
  3120. });
  3121. }
  3122. };
  3123. return chartNavigation;
  3124. }());
  3125. (function (H, chartNavigationMixin) {
  3126. /**
  3127. * (c) 2009-2017 Highsoft, Black Label
  3128. *
  3129. * License: www.highcharts.com/license
  3130. */
  3131. var doc = H.doc,
  3132. addEvent = H.addEvent,
  3133. pick = H.pick,
  3134. merge = H.merge,
  3135. extend = H.extend,
  3136. isNumber = H.isNumber,
  3137. fireEvent = H.fireEvent,
  3138. isArray = H.isArray,
  3139. isObject = H.isObject,
  3140. objectEach = H.objectEach,
  3141. PREFIX = 'highcharts-';
  3142. /**
  3143. * @private
  3144. * @interface bindingsUtils
  3145. */
  3146. var bindingsUtils = {
  3147. /**
  3148. * Update size of background (rect) in some annotations: Measure, Simple
  3149. * Rect.
  3150. *
  3151. * @private
  3152. * @function bindingsUtils.updateRectSize
  3153. *
  3154. * @param {global.Event} event
  3155. * Normalized browser event
  3156. *
  3157. * @param {Highcharts.Annotation} annotation
  3158. * Annotation to be updated
  3159. */
  3160. updateRectSize: function (event, annotation) {
  3161. var options = annotation.options.typeOptions,
  3162. x = this.chart.xAxis[0].toValue(event.chartX),
  3163. y = this.chart.yAxis[0].toValue(event.chartY),
  3164. width = x - options.point.x,
  3165. height = options.point.y - y;
  3166. annotation.update({
  3167. typeOptions: {
  3168. background: {
  3169. width: width,
  3170. height: height
  3171. }
  3172. }
  3173. });
  3174. },
  3175. /**
  3176. * Get field type according to value
  3177. *
  3178. * @private
  3179. * @function bindingsUtils.getFieldType
  3180. *
  3181. * @param {*} value
  3182. * Atomic type (one of: string, number, boolean)
  3183. *
  3184. * @return {string}
  3185. * Field type (one of: text, number, checkbox)
  3186. */
  3187. getFieldType: function (value) {
  3188. return {
  3189. 'string': 'text',
  3190. 'number': 'number',
  3191. 'boolean': 'checkbox'
  3192. }[typeof value];
  3193. }
  3194. };
  3195. H.NavigationBindings = function (chart, options) {
  3196. this.chart = chart;
  3197. this.options = options;
  3198. this.eventsToUnbind = [];
  3199. this.container = doc.getElementsByClassName(
  3200. this.options.bindingsClassName
  3201. );
  3202. };
  3203. // Define which options from annotations should show up in edit box:
  3204. H.NavigationBindings.annotationsEditable = {
  3205. // `typeOptions` are always available
  3206. // Nested and shared options:
  3207. nestedOptions: {
  3208. labelOptions: ['style', 'format', 'backgroundColor'],
  3209. labels: ['style'],
  3210. label: ['style'],
  3211. style: ['fontSize', 'color'],
  3212. background: ['fill', 'strokeWidth', 'stroke'],
  3213. innerBackground: ['fill', 'strokeWidth', 'stroke'],
  3214. outerBackground: ['fill', 'strokeWidth', 'stroke'],
  3215. shapeOptions: ['fill', 'strokeWidth', 'stroke'],
  3216. shapes: ['fill', 'strokeWidth', 'stroke'],
  3217. line: ['strokeWidth', 'stroke'],
  3218. backgroundColors: [true],
  3219. connector: ['fill', 'strokeWidth', 'stroke'],
  3220. crosshairX: ['strokeWidth', 'stroke'],
  3221. crosshairY: ['strokeWidth', 'stroke']
  3222. },
  3223. // Simple shapes:
  3224. circle: ['shapes'],
  3225. verticalLine: [],
  3226. label: ['labelOptions'],
  3227. // Measure
  3228. measure: ['background', 'crosshairY', 'crosshairX'],
  3229. // Others:
  3230. fibonacci: [],
  3231. tunnel: ['background', 'line', 'height'],
  3232. pitchfork: ['innerBackground', 'outerBackground'],
  3233. rect: ['shapes'],
  3234. // Crooked lines, elliots, arrows etc:
  3235. crookedLine: []
  3236. };
  3237. // Define non editable fields per annotation, for example Rectangle inherits
  3238. // options from Measure, but crosshairs are not available
  3239. H.NavigationBindings.annotationsNonEditable = {
  3240. rectangle: ['crosshairX', 'crosshairY', 'label']
  3241. };
  3242. extend(H.NavigationBindings.prototype, {
  3243. // Private properties added by bindings:
  3244. // Active (selected) annotation that is editted through popup/forms
  3245. // activeAnnotation: Annotation
  3246. // Holder for current step, used on mouse move to update bound object
  3247. // mouseMoveEvent: function () {}
  3248. // Next event in `step` array to be called on chart's click
  3249. // nextEvent: function () {}
  3250. // Index in the `step` array of the current event
  3251. // stepIndex: 0
  3252. // Flag to determine if current binding has steps
  3253. // steps: true|false
  3254. // Bindings holder for all events
  3255. // selectedButton: {}
  3256. // Holder for user options, returned from `start` event, and passed on to
  3257. // `step`'s' and `end`.
  3258. // currentUserDetails: {}
  3259. /**
  3260. * Initi all events conencted to NavigationBindings.
  3261. *
  3262. * @private
  3263. * @function Highcharts.NavigationBindings#initEvents
  3264. */
  3265. initEvents: function () {
  3266. var navigation = this,
  3267. chart = navigation.chart,
  3268. bindingsContainer = navigation.container,
  3269. options = navigation.options;
  3270. // Shorthand object for getting events for buttons:
  3271. navigation.boundClassNames = {};
  3272. objectEach(options.bindings, function (value) {
  3273. navigation.boundClassNames[value.className] = value;
  3274. });
  3275. // Handle multiple containers with the same class names:
  3276. [].forEach.call(bindingsContainer, function (subContainer) {
  3277. navigation.eventsToUnbind.push(
  3278. addEvent(
  3279. subContainer,
  3280. 'click',
  3281. function (event) {
  3282. var bindings = navigation.getButtonEvents(
  3283. bindingsContainer,
  3284. event
  3285. );
  3286. if (bindings) {
  3287. navigation.bindingsButtonClick(
  3288. bindings.button,
  3289. bindings.events,
  3290. event
  3291. );
  3292. }
  3293. }
  3294. )
  3295. );
  3296. });
  3297. objectEach(options.events || {}, function (callback, eventName) {
  3298. navigation.eventsToUnbind.push(
  3299. addEvent(
  3300. navigation,
  3301. eventName,
  3302. callback
  3303. )
  3304. );
  3305. });
  3306. navigation.eventsToUnbind.push(
  3307. addEvent(chart.container, 'click', function (e) {
  3308. if (
  3309. !chart.cancelClick &&
  3310. chart.isInsidePlot(
  3311. e.chartX - chart.plotLeft,
  3312. e.chartY - chart.plotTop
  3313. )
  3314. ) {
  3315. navigation.bindingsChartClick(this, e);
  3316. }
  3317. })
  3318. );
  3319. navigation.eventsToUnbind.push(
  3320. addEvent(chart.container, 'mousemove', function (e) {
  3321. navigation.bindingsContainerMouseMove(this, e);
  3322. })
  3323. );
  3324. },
  3325. /**
  3326. * Common chart.update() delegation, shared between bindings and exporting.
  3327. *
  3328. * @private
  3329. * @function Highcharts.NavigationBindings#initUpdate
  3330. */
  3331. initUpdate: function () {
  3332. var navigation = this;
  3333. chartNavigationMixin.addUpdate(
  3334. function (options) {
  3335. navigation.update(options);
  3336. },
  3337. this.chart
  3338. );
  3339. },
  3340. /**
  3341. * Hook for click on a button, method selcts/unselects buttons,
  3342. * then calls `bindings.init` callback.
  3343. *
  3344. * @private
  3345. * @function Highcharts.NavigationBindings#bindingsButtonClick
  3346. *
  3347. * @param {Highcharts.HTMLDOMElement} [button]
  3348. * Clicked button
  3349. *
  3350. * @param {object} [events]
  3351. * Events passed down from bindings (`init`, `start`, `step`, `end`)
  3352. *
  3353. * @param {global.Event} [clickEvent]
  3354. * Browser's click event
  3355. */
  3356. bindingsButtonClick: function (button, events, clickEvent) {
  3357. var navigation = this,
  3358. chart = navigation.chart;
  3359. if (navigation.selectedButtonElement) {
  3360. fireEvent(
  3361. navigation,
  3362. 'deselectButton',
  3363. { button: navigation.selectedButtonElement }
  3364. );
  3365. if (navigation.nextEvent) {
  3366. // Remove in-progress annotations adders:
  3367. if (
  3368. navigation.currentUserDetails &&
  3369. navigation.currentUserDetails.coll === 'annotations'
  3370. ) {
  3371. chart.removeAnnotation(navigation.currentUserDetails);
  3372. }
  3373. navigation.mouseMoveEvent = navigation.nextEvent = false;
  3374. }
  3375. }
  3376. navigation.selectedButton = events;
  3377. navigation.selectedButtonElement = button;
  3378. fireEvent(navigation, 'selectButton', { button: button });
  3379. // Call "init" event, for example to open modal window
  3380. if (events.init) {
  3381. events.init.call(navigation, button, clickEvent);
  3382. }
  3383. if (events.start || events.steps) {
  3384. chart.renderer.boxWrapper.addClass(PREFIX + 'draw-mode');
  3385. }
  3386. },
  3387. /**
  3388. * Hook for click on a chart, first click on a chart calls `start` event,
  3389. * then on all subsequent clicks iterate over `steps` array.
  3390. * When finished, calls `end` event.
  3391. *
  3392. * @private
  3393. * @function Highcharts.NavigationBindings#bindingsChartClick
  3394. *
  3395. * @param {Highcharts.Chart} chart
  3396. * Chart that click was performed on.
  3397. *
  3398. * @param {global.Event} clickEvent
  3399. * Browser's click event.
  3400. */
  3401. bindingsChartClick: function (chartContainer, clickEvent) {
  3402. var navigation = this,
  3403. chart = navigation.chart,
  3404. selectedButton = navigation.selectedButton,
  3405. svgContainer = chart.renderer.boxWrapper;
  3406. if (
  3407. navigation.activeAnnotation &&
  3408. !clickEvent.activeAnnotation &&
  3409. // Element could be removed in the child action, e.g. button
  3410. clickEvent.target.parentNode &&
  3411. // TO DO: Polyfill for IE11?
  3412. !clickEvent.target.closest('.' + PREFIX + 'popup')
  3413. ) {
  3414. fireEvent(navigation, 'closePopup');
  3415. navigation.deselectAnnotation();
  3416. }
  3417. if (!selectedButton || !selectedButton.start) {
  3418. return;
  3419. }
  3420. if (!navigation.nextEvent) {
  3421. // Call init method:
  3422. navigation.currentUserDetails = selectedButton.start.call(
  3423. navigation,
  3424. clickEvent
  3425. );
  3426. // If steps exists (e.g. Annotations), bind them:
  3427. if (selectedButton.steps) {
  3428. navigation.stepIndex = 0;
  3429. navigation.steps = true;
  3430. navigation.mouseMoveEvent = navigation.nextEvent =
  3431. selectedButton.steps[navigation.stepIndex];
  3432. } else {
  3433. fireEvent(
  3434. navigation,
  3435. 'deselectButton',
  3436. { button: navigation.selectedButtonElement }
  3437. );
  3438. svgContainer.removeClass(PREFIX + 'draw-mode');
  3439. navigation.steps = false;
  3440. navigation.selectedButton = null;
  3441. // First click is also the last one:
  3442. if (selectedButton.end) {
  3443. selectedButton.end.call(
  3444. navigation,
  3445. clickEvent,
  3446. navigation.currentUserDetails
  3447. );
  3448. }
  3449. }
  3450. } else {
  3451. navigation.nextEvent(
  3452. clickEvent,
  3453. navigation.currentUserDetails
  3454. );
  3455. if (navigation.steps) {
  3456. navigation.stepIndex++;
  3457. if (selectedButton.steps[navigation.stepIndex]) {
  3458. // If we have more steps, bind them one by one:
  3459. navigation.mouseMoveEvent = navigation.nextEvent =
  3460. selectedButton.steps[navigation.stepIndex];
  3461. } else {
  3462. fireEvent(
  3463. navigation,
  3464. 'deselectButton',
  3465. { button: navigation.selectedButtonElement }
  3466. );
  3467. svgContainer.removeClass(PREFIX + 'draw-mode');
  3468. // That was the last step, call end():
  3469. if (selectedButton.end) {
  3470. selectedButton.end.call(
  3471. navigation,
  3472. clickEvent,
  3473. navigation.currentUserDetails
  3474. );
  3475. }
  3476. navigation.nextEvent = false;
  3477. navigation.mouseMoveEvent = false;
  3478. navigation.selectedButton = null;
  3479. }
  3480. }
  3481. }
  3482. },
  3483. /**
  3484. * Hook for mouse move on a chart's container. It calls current step.
  3485. *
  3486. * @private
  3487. * @function Highcharts.NavigationBindings#bindingsContainerMouseMove
  3488. *
  3489. * @param {Highcharts.HTMLDOMElement} container
  3490. * Chart's container.
  3491. *
  3492. * @param {global.Event} moveEvent
  3493. * Browser's move event.
  3494. */
  3495. bindingsContainerMouseMove: function (container, moveEvent) {
  3496. if (this.mouseMoveEvent) {
  3497. this.mouseMoveEvent(
  3498. moveEvent,
  3499. this.currentUserDetails
  3500. );
  3501. }
  3502. },
  3503. /**
  3504. * Translate fields (e.g. `params.period` or `marker.styles.color`) to
  3505. * Highcharts options object (e.g. `{ params: { period } }`).
  3506. *
  3507. * @private
  3508. * @function Highcharts.NavigationBindings#fieldsToOptions
  3509. *
  3510. * @param {object} fields
  3511. * Fields from popup form.
  3512. *
  3513. * @param {object} config
  3514. * Default config to be modified.
  3515. *
  3516. * @return {object}
  3517. * Modified config
  3518. */
  3519. fieldsToOptions: function (fields, config) {
  3520. objectEach(fields, function (value, field) {
  3521. var parsedValue = parseFloat(value),
  3522. path = field.split('.'),
  3523. parent = config,
  3524. pathLength = path.length - 1;
  3525. // If it's a number (not "forma" options), parse it:
  3526. if (
  3527. isNumber(parsedValue) &&
  3528. !value.match(/px/g) &&
  3529. !field.match(/format/g)
  3530. ) {
  3531. value = parsedValue;
  3532. }
  3533. // Remove empty strings or values like 0
  3534. if (value !== '' && value !== 'undefined') {
  3535. path.forEach(function (name, index) {
  3536. var nextName = pick(path[index + 1], '');
  3537. if (pathLength === index) {
  3538. // Last index, put value:
  3539. parent[name] = value;
  3540. } else if (!parent[name]) {
  3541. // Create middle property:
  3542. parent[name] = nextName.match(/\d/g) ? [] : {};
  3543. parent = parent[name];
  3544. } else {
  3545. // Jump into next property
  3546. parent = parent[name];
  3547. }
  3548. });
  3549. }
  3550. });
  3551. return config;
  3552. },
  3553. /**
  3554. * Shorthand method to deselect an annotation.
  3555. *
  3556. * @function Highcharts.NavigationBindings#deselectAnnotation
  3557. */
  3558. deselectAnnotation: function () {
  3559. if (this.activeAnnotation) {
  3560. this.activeAnnotation.setControlPointsVisibility(false);
  3561. this.activeAnnotation = false;
  3562. }
  3563. },
  3564. /**
  3565. * Generates API config for popup in the same format as options for
  3566. * Annotation object.
  3567. *
  3568. * @function Highcharts.NavigationBindings#annotationToFields
  3569. *
  3570. * @param {Highcharts.Annotation} annotation
  3571. * Annotations object
  3572. *
  3573. * @return {object}
  3574. * Annotation options to be displayed in popup box
  3575. */
  3576. annotationToFields: function (annotation) {
  3577. var options = annotation.options,
  3578. editables = H.NavigationBindings.annotationsEditable,
  3579. nestedEditables = editables.nestedOptions,
  3580. getFieldType = this.utils.getFieldType,
  3581. type = pick(
  3582. options.type,
  3583. options.shapes && options.shapes[0] &&
  3584. options.shapes[0].type,
  3585. options.labels && options.labels[0] &&
  3586. options.labels[0].itemType,
  3587. 'label'
  3588. ),
  3589. nonEditables = H.NavigationBindings
  3590. .annotationsNonEditable[options.langKey] || [],
  3591. visualOptions = {
  3592. langKey: options.langKey,
  3593. type: type
  3594. };
  3595. /**
  3596. * Nested options traversing. Method goes down to the options and copies
  3597. * allowed options (with values) to new object, which is last parameter:
  3598. * "parent".
  3599. *
  3600. * @private
  3601. * @function Highcharts.NavigationBindings#annotationToFields.traverse
  3602. *
  3603. * @param {*} option
  3604. * Atomic type or object/array
  3605. *
  3606. * @param {string} key
  3607. * Option name, for example "visible" or "x", "y"
  3608. *
  3609. * @param {object} allowed
  3610. * Editables from H.NavigationBindings.annotationsEditable
  3611. *
  3612. * @param {object} parent
  3613. * Where new options will be assigned
  3614. */
  3615. function traverse(option, key, parentEditables, parent) {
  3616. var nextParent;
  3617. if (
  3618. parentEditables &&
  3619. nonEditables.indexOf(key) === -1 &&
  3620. (
  3621. (
  3622. parentEditables.indexOf &&
  3623. parentEditables.indexOf(key)
  3624. ) >= 0 ||
  3625. parentEditables[key] || // nested array
  3626. parentEditables === true // simple array
  3627. )
  3628. ) {
  3629. // Roots:
  3630. if (isArray(option)) {
  3631. parent[key] = [];
  3632. option.forEach(function (arrayOption, i) {
  3633. if (!isObject(arrayOption)) {
  3634. // Simple arrays, e.g. [String, Number, Boolean]
  3635. traverse(
  3636. arrayOption,
  3637. 0,
  3638. nestedEditables[key],
  3639. parent[key]
  3640. );
  3641. } else {
  3642. // Advanced arrays, e.g. [Object, Object]
  3643. parent[key][i] = {};
  3644. objectEach(
  3645. arrayOption,
  3646. function (nestedOption, nestedKey) {
  3647. traverse(
  3648. nestedOption,
  3649. nestedKey,
  3650. nestedEditables[key],
  3651. parent[key][i]
  3652. );
  3653. }
  3654. );
  3655. }
  3656. });
  3657. } else if (isObject(option)) {
  3658. nextParent = {};
  3659. if (isArray(parent)) {
  3660. parent.push(nextParent);
  3661. nextParent[key] = {};
  3662. nextParent = nextParent[key];
  3663. } else {
  3664. parent[key] = nextParent;
  3665. }
  3666. objectEach(option, function (nestedOption, nestedKey) {
  3667. traverse(
  3668. nestedOption,
  3669. nestedKey,
  3670. key === 0 ? parentEditables : nestedEditables[key],
  3671. nextParent
  3672. );
  3673. });
  3674. } else {
  3675. // Leaf:
  3676. if (key === 'format') {
  3677. parent[key] = [
  3678. H.format(
  3679. option,
  3680. annotation.labels[0].points[0]
  3681. ).toString(),
  3682. 'text'
  3683. ];
  3684. } else if (isArray(parent)) {
  3685. parent.push([option, getFieldType(option)]);
  3686. } else {
  3687. parent[key] = [option, getFieldType(option)];
  3688. }
  3689. }
  3690. }
  3691. }
  3692. objectEach(options, function (option, key) {
  3693. if (key === 'typeOptions') {
  3694. visualOptions[key] = {};
  3695. objectEach(options[key], function (typeOption, typeKey) {
  3696. traverse(
  3697. typeOption,
  3698. typeKey,
  3699. nestedEditables,
  3700. visualOptions[key],
  3701. true
  3702. );
  3703. });
  3704. } else {
  3705. traverse(option, key, editables[type], visualOptions);
  3706. }
  3707. });
  3708. return visualOptions;
  3709. },
  3710. /**
  3711. * Get all class names for all parents in the element. Iterates until finds
  3712. * main container.
  3713. *
  3714. * @function Highcharts.NavigationBindings#getClickedClassNames
  3715. *
  3716. * @param {Highcharts.HTMLDOMElement}
  3717. * Container that event is bound to.
  3718. *
  3719. * @param {global.Event} event
  3720. * Browser's event.
  3721. *
  3722. * @return {Array<string>}
  3723. * Array of class names with corresponding elements
  3724. */
  3725. getClickedClassNames: function (container, event) {
  3726. var element = event.target,
  3727. classNames = [],
  3728. elemClassName;
  3729. while (element) {
  3730. elemClassName = H.attr(element, 'class');
  3731. if (elemClassName) {
  3732. classNames = classNames.concat(
  3733. elemClassName.split(' ').map(
  3734. function (name) { // eslint-disable-line no-loop-func
  3735. return [
  3736. name,
  3737. element
  3738. ];
  3739. }
  3740. )
  3741. );
  3742. }
  3743. element = element.parentNode;
  3744. if (element === container) {
  3745. return classNames;
  3746. }
  3747. }
  3748. return classNames;
  3749. },
  3750. /**
  3751. * Get events bound to a button. It's a custom event delegation to find all
  3752. * events connected to the element.
  3753. *
  3754. * @function Highcharts.NavigationBindings#getButtonEvents
  3755. *
  3756. * @param {Highcharts.HTMLDOMElement}
  3757. * Container that event is bound to.
  3758. *
  3759. * @param {global.Event} event
  3760. * Browser's event.
  3761. *
  3762. * @return {object}
  3763. * Oject with events (init, start, steps, and end)
  3764. */
  3765. getButtonEvents: function (container, event) {
  3766. var navigation = this,
  3767. classNames = this.getClickedClassNames(container, event),
  3768. bindings;
  3769. classNames.forEach(function (className) {
  3770. if (navigation.boundClassNames[className[0]] && !bindings) {
  3771. bindings = {
  3772. events: navigation.boundClassNames[className[0]],
  3773. button: className[1]
  3774. };
  3775. }
  3776. });
  3777. return bindings;
  3778. },
  3779. /**
  3780. * Bindings are just events, so the whole update process is simply
  3781. * removing old events and adding new ones.
  3782. *
  3783. * @private
  3784. * @function Highcharts.NavigationBindings#update
  3785. */
  3786. update: function (options) {
  3787. this.options = merge(true, this.options, options);
  3788. this.removeEvents();
  3789. this.initEvents();
  3790. },
  3791. /**
  3792. * Remove all events created in the navigation.
  3793. *
  3794. * @private
  3795. * @function Highcharts.NavigationBindings#removeEvents
  3796. */
  3797. removeEvents: function () {
  3798. this.eventsToUnbind.forEach(function (unbinder) {
  3799. unbinder();
  3800. });
  3801. },
  3802. destroy: function () {
  3803. this.removeEvents();
  3804. },
  3805. /**
  3806. * General utils for bindings
  3807. *
  3808. * @private
  3809. * @name Highcharts.NavigationBindings#utils
  3810. * @type {bindingsUtils}
  3811. */
  3812. utils: bindingsUtils
  3813. });
  3814. H.Chart.prototype.initNavigationBindings = function () {
  3815. var chart = this,
  3816. options = chart.options;
  3817. if (options && options.navigation && options.navigation.bindings) {
  3818. chart.navigationBindings = new H.NavigationBindings(
  3819. chart,
  3820. options.navigation
  3821. );
  3822. chart.navigationBindings.initEvents();
  3823. chart.navigationBindings.initUpdate();
  3824. }
  3825. };
  3826. addEvent(H.Chart, 'load', function () {
  3827. this.initNavigationBindings();
  3828. });
  3829. addEvent(H.Chart, 'destroy', function () {
  3830. if (this.navigationBindings) {
  3831. this.navigationBindings.destroy();
  3832. }
  3833. });
  3834. addEvent(H.NavigationBindings, 'deselectButton', function () {
  3835. this.selectedButtonElement = null;
  3836. });
  3837. // Show edit-annotation form:
  3838. function selectableAnnotation(annotationType) {
  3839. var originalClick = annotationType.prototype.defaultOptions.events &&
  3840. annotationType.prototype.defaultOptions.events.click;
  3841. function selectAndshowPopup(event) {
  3842. var annotation = this,
  3843. navigation = annotation.chart.navigationBindings,
  3844. prevAnnotation = navigation.activeAnnotation;
  3845. if (originalClick) {
  3846. originalClick.click.call(annotation, event);
  3847. }
  3848. if (prevAnnotation !== annotation) {
  3849. // Select current:
  3850. navigation.deselectAnnotation();
  3851. navigation.activeAnnotation = annotation;
  3852. annotation.setControlPointsVisibility(true);
  3853. fireEvent(
  3854. navigation,
  3855. 'showPopup',
  3856. {
  3857. annotation: annotation,
  3858. formType: 'annotation-toolbar',
  3859. options: navigation.annotationToFields(annotation),
  3860. onSubmit: function (data) {
  3861. var config = {},
  3862. typeOptions;
  3863. if (data.actionType === 'remove') {
  3864. navigation.activeAnnotation = false;
  3865. navigation.chart.removeAnnotation(annotation);
  3866. } else {
  3867. navigation.fieldsToOptions(data.fields, config);
  3868. navigation.deselectAnnotation();
  3869. typeOptions = config.typeOptions;
  3870. if (annotation.options.type === 'measure') {
  3871. // Manually disable crooshars according to
  3872. // stroke width of the shape:
  3873. typeOptions.crosshairY.enabled =
  3874. typeOptions.crosshairY.strokeWidth !== 0;
  3875. typeOptions.crosshairX.enabled =
  3876. typeOptions.crosshairX.strokeWidth !== 0;
  3877. }
  3878. annotation.update(config);
  3879. }
  3880. }
  3881. }
  3882. );
  3883. } else {
  3884. // Deselect current:
  3885. navigation.deselectAnnotation();
  3886. fireEvent(navigation, 'closePopup');
  3887. }
  3888. // Let bubble event to chart.click:
  3889. event.activeAnnotation = true;
  3890. }
  3891. H.merge(
  3892. true,
  3893. annotationType.prototype.defaultOptions.events,
  3894. {
  3895. click: selectAndshowPopup
  3896. }
  3897. );
  3898. }
  3899. if (H.Annotation) {
  3900. // Basic shapes:
  3901. selectableAnnotation(H.Annotation);
  3902. // Advanced annotations:
  3903. H.objectEach(H.Annotation.types, function (annotationType) {
  3904. selectableAnnotation(annotationType);
  3905. });
  3906. }
  3907. H.setOptions({
  3908. /**
  3909. * @optionparent lang
  3910. */
  3911. lang: {
  3912. /**
  3913. * Configure the Popup strings in the chart. Requires the
  3914. * `annotations.js` or `annotations-advanced.src.js` module to be
  3915. * loaded.
  3916. *
  3917. * @since 7.0.0
  3918. * @type {Object}
  3919. * @product highcharts highstock
  3920. */
  3921. navigation: {
  3922. /**
  3923. * Translations for all field names used in popup.
  3924. *
  3925. * @product highcharts highstock
  3926. * @type {Object}
  3927. */
  3928. popup: {
  3929. simpleShapes: 'Simple shapes',
  3930. lines: 'Lines',
  3931. circle: 'Circle',
  3932. rectangle: 'Rectangle',
  3933. label: 'Label',
  3934. shapeOptions: 'Shape options',
  3935. typeOptions: 'Details',
  3936. fill: 'Fill',
  3937. format: 'Text',
  3938. strokeWidth: 'Line width',
  3939. stroke: 'Line color',
  3940. title: 'Title',
  3941. name: 'Name',
  3942. labelOptions: 'Label options',
  3943. labels: 'Labels',
  3944. backgroundColor: 'Background color',
  3945. backgroundColors: 'Background colors',
  3946. borderColor: 'Border color',
  3947. borderRadius: 'Border radius',
  3948. borderWidth: 'Border width',
  3949. style: 'Style',
  3950. padding: 'Padding',
  3951. fontSize: 'Font size',
  3952. color: 'Color',
  3953. height: 'Height',
  3954. shapes: 'Shape options'
  3955. }
  3956. }
  3957. },
  3958. /**
  3959. * @optionparent navigation
  3960. * @product highcharts highstock
  3961. */
  3962. navigation: {
  3963. /**
  3964. * A CSS class name where all bindings will be attached to. Multiple
  3965. * charts on the same page should have separate class names to prevent
  3966. * duplicating events.
  3967. *
  3968. * @since 7.0.0
  3969. * @type {string}
  3970. */
  3971. bindingsClassName: 'highcharts-bindings-wrapper',
  3972. /**
  3973. * Bindings definitions for custom HTML buttons. Each binding implements
  3974. * simple event-driven interface:
  3975. *
  3976. * - `className`: classname used to bind event to
  3977. *
  3978. * - `init`: initial event, fired on button click
  3979. *
  3980. * - `start`: fired on first click on a chart
  3981. *
  3982. * - `steps`: array of sequential events fired one after another on each
  3983. * of users clicks
  3984. *
  3985. * - `end`: last event to be called after last step event
  3986. *
  3987. * @type {Highcharts.Dictionary<Highcharts.StockToolsBindingsObject>|*}
  3988. * @sample stock/stocktools/stocktools-thresholds
  3989. * Custom bindings in Highstock
  3990. * @since 7.0.0
  3991. * @product highcharts highstock
  3992. */
  3993. bindings: {
  3994. /**
  3995. * A circle annotation bindings. Includes `start` and one event in
  3996. * `steps` array.
  3997. *
  3998. * @type {Highcharts.StockToolsBindingsObject}
  3999. * @default {"className": "highcharts-circle-annotation", "start": function() {}, "steps": [function() {}]}
  4000. */
  4001. circleAnnotation: {
  4002. /** @ignore */
  4003. className: 'highcharts-circle-annotation',
  4004. /** @ignore */
  4005. start: function (e) {
  4006. var x = this.chart.xAxis[0].toValue(e.chartX),
  4007. y = this.chart.yAxis[0].toValue(e.chartY),
  4008. annotation;
  4009. annotation = this.chart.addAnnotation({
  4010. langKey: 'circle',
  4011. shapes: [{
  4012. type: 'circle',
  4013. point: {
  4014. xAxis: 0,
  4015. yAxis: 0,
  4016. x: x,
  4017. y: y
  4018. },
  4019. r: 5,
  4020. controlPoints: [{
  4021. positioner: function (target) {
  4022. var xy = H.Annotation.MockPoint
  4023. .pointToPixels(
  4024. target.points[0]
  4025. ),
  4026. r = target.options.r;
  4027. return {
  4028. x: xy.x + r * Math.cos(Math.PI / 4) -
  4029. this.graphic.width / 2,
  4030. y: xy.y + r * Math.sin(Math.PI / 4) -
  4031. this.graphic.height / 2
  4032. };
  4033. },
  4034. events: {
  4035. // TRANSFORM RADIUS ACCORDING TO Y
  4036. // TRANSLATION
  4037. drag: function (e, target) {
  4038. var annotation = target.annotation,
  4039. position = this
  4040. .mouseMoveToTranslation(e);
  4041. target.setRadius(
  4042. Math.max(
  4043. target.options.r +
  4044. position.y /
  4045. Math.sin(Math.PI / 4),
  4046. 5
  4047. )
  4048. );
  4049. annotation.options.shapes[0] =
  4050. annotation.userOptions.shapes[0] =
  4051. target.options;
  4052. target.redraw(false);
  4053. }
  4054. }
  4055. }]
  4056. }]
  4057. });
  4058. return annotation;
  4059. },
  4060. /** @ignore */
  4061. steps: [
  4062. function (e, annotation) {
  4063. var point = annotation.options.shapes[0].point,
  4064. x = this.chart.xAxis[0].toPixels(point.x),
  4065. y = this.chart.yAxis[0].toPixels(point.y),
  4066. distance = Math.max(
  4067. Math.sqrt(
  4068. Math.pow(x - e.chartX, 2) +
  4069. Math.pow(y - e.chartY, 2)
  4070. ),
  4071. 5
  4072. );
  4073. annotation.update({
  4074. shapes: [{
  4075. r: distance
  4076. }]
  4077. });
  4078. }
  4079. ]
  4080. },
  4081. /**
  4082. * A rectangle annotation bindings. Includes `start` and one event
  4083. * in `steps` array.
  4084. *
  4085. * @type {Highcharts.StockToolsBindingsObject}
  4086. * @default {"className": "highcharts-rectangle-annotation", "start": function() {}, "steps": [function() {}]}
  4087. */
  4088. rectangleAnnotation: {
  4089. /** @ignore */
  4090. className: 'highcharts-rectangle-annotation',
  4091. /** @ignore */
  4092. start: function (e) {
  4093. var x = this.chart.xAxis[0].toValue(e.chartX),
  4094. y = this.chart.yAxis[0].toValue(e.chartY),
  4095. options = {
  4096. langKey: 'rectangle',
  4097. shapes: [{
  4098. type: 'rect',
  4099. point: {
  4100. x: x,
  4101. y: y,
  4102. xAxis: 0,
  4103. yAxis: 0
  4104. },
  4105. width: 5,
  4106. height: 5,
  4107. controlPoints: [{
  4108. positioner: function (target) {
  4109. var xy = H.Annotation.MockPoint
  4110. .pointToPixels(
  4111. target.points[0]
  4112. );
  4113. return {
  4114. x: xy.x + target.options.width - 4,
  4115. y: xy.y + target.options.height - 4
  4116. };
  4117. },
  4118. events: {
  4119. drag: function (e, target) {
  4120. var annotation = target.annotation,
  4121. xy = this
  4122. .mouseMoveToTranslation(e);
  4123. target.options.width = Math.max(
  4124. target.options.width + xy.x,
  4125. 5
  4126. );
  4127. target.options.height = Math.max(
  4128. target.options.height + xy.y,
  4129. 5
  4130. );
  4131. annotation.options.shapes[0] =
  4132. target.options;
  4133. annotation.userOptions.shapes[0] =
  4134. target.options;
  4135. target.redraw(false);
  4136. }
  4137. }
  4138. }]
  4139. }]
  4140. };
  4141. return this.chart.addAnnotation(options);
  4142. },
  4143. /** @ignore */
  4144. steps: [
  4145. function (e, annotation) {
  4146. var xAxis = this.chart.xAxis[0],
  4147. yAxis = this.chart.yAxis[0],
  4148. point = annotation.options.shapes[0].point,
  4149. x = xAxis.toPixels(point.x),
  4150. y = yAxis.toPixels(point.y),
  4151. width = Math.max(e.chartX - x, 5),
  4152. height = Math.max(e.chartY - y, 5);
  4153. annotation.update({
  4154. shapes: [{
  4155. width: width,
  4156. height: height,
  4157. point: {
  4158. x: point.x,
  4159. y: point.y
  4160. }
  4161. }]
  4162. });
  4163. }
  4164. ]
  4165. },
  4166. /**
  4167. * A label annotation bindings. Includes `start` event only.
  4168. *
  4169. * @type {Highcharts.StockToolsBindingsObject}
  4170. * @default {"className": "highcharts-label-annotation", "start": function() {}, "steps": [function() {}]}
  4171. */
  4172. labelAnnotation: {
  4173. /** @ignore */
  4174. className: 'highcharts-label-annotation',
  4175. /** @ignore */
  4176. start: function (e) {
  4177. var x = this.chart.xAxis[0].toValue(e.chartX),
  4178. y = this.chart.yAxis[0].toValue(e.chartY);
  4179. this.chart.addAnnotation({
  4180. langKey: 'label',
  4181. labelOptions: {
  4182. format: '{y:.2f}'
  4183. },
  4184. labels: [{
  4185. point: {
  4186. x: x,
  4187. y: y,
  4188. xAxis: 0,
  4189. yAxis: 0
  4190. },
  4191. controlPoints: [{
  4192. symbol: 'triangle-down',
  4193. positioner: function (target) {
  4194. if (!target.graphic.placed) {
  4195. return {
  4196. x: 0,
  4197. y: -9e7
  4198. };
  4199. }
  4200. var xy = H.Annotation.MockPoint
  4201. .pointToPixels(
  4202. target.points[0]
  4203. );
  4204. return {
  4205. x: xy.x - this.graphic.width / 2,
  4206. y: xy.y - this.graphic.height / 2
  4207. };
  4208. },
  4209. // TRANSLATE POINT/ANCHOR
  4210. events: {
  4211. drag: function (e, target) {
  4212. var xy = this.mouseMoveToTranslation(e);
  4213. target.translatePoint(xy.x, xy.y);
  4214. target.annotation.labels[0].options =
  4215. target.options;
  4216. target.redraw(false);
  4217. }
  4218. }
  4219. }, {
  4220. symbol: 'square',
  4221. positioner: function (target) {
  4222. if (!target.graphic.placed) {
  4223. return {
  4224. x: 0,
  4225. y: -9e7
  4226. };
  4227. }
  4228. return {
  4229. x: target.graphic.alignAttr.x -
  4230. this.graphic.width / 2,
  4231. y: target.graphic.alignAttr.y -
  4232. this.graphic.height / 2
  4233. };
  4234. },
  4235. // TRANSLATE POSITION WITHOUT CHANGING THE
  4236. // ANCHOR
  4237. events: {
  4238. drag: function (e, target) {
  4239. var xy = this.mouseMoveToTranslation(e);
  4240. target.translate(xy.x, xy.y);
  4241. target.annotation.labels[0].options =
  4242. target.options;
  4243. target.redraw(false);
  4244. }
  4245. }
  4246. }],
  4247. overflow: 'none',
  4248. crop: true
  4249. }]
  4250. });
  4251. }
  4252. }
  4253. },
  4254. /**
  4255. * A `showPopup` event. Fired when selecting for example an annotation.
  4256. *
  4257. * @type {Function}
  4258. * @apioption navigation.events.showPopup
  4259. */
  4260. /**
  4261. * A `hidePopop` event. Fired when Popup should be hidden, for exampole
  4262. * when clicking on an annotation again.
  4263. *
  4264. * @type {Function}
  4265. * @apioption navigation.events.hidePopup
  4266. */
  4267. /**
  4268. * Event fired on a button click.
  4269. *
  4270. * @type {Function}
  4271. * @sample highcharts/annotations/gui/
  4272. * Change icon in a dropddown on event
  4273. * @sample highcharts/annotations/gui-buttons/
  4274. * Change button class on event
  4275. * @apioption navigation.events.selectButton
  4276. */
  4277. /**
  4278. * Event fired when button state should change, for example after
  4279. * adding an annotation.
  4280. *
  4281. * @type {Function}
  4282. * @sample highcharts/annotations/gui/
  4283. * Change icon in a dropddown on event
  4284. * @sample highcharts/annotations/gui-buttons/
  4285. * Change button class on event
  4286. * @apioption navigation.events.deselectButton
  4287. */
  4288. /**
  4289. * Events to communicate between Stock Tools and custom GUI.
  4290. *
  4291. * @since 7.0.0
  4292. * @product highcharts highstock
  4293. * @optionparent navigation.events
  4294. */
  4295. events: {}
  4296. }
  4297. });
  4298. }(Highcharts, chartNavigation));
  4299. (function (H) {
  4300. /**
  4301. * Popup generator for Stock tools
  4302. *
  4303. * (c) 2009-2017 Sebastian Bochan
  4304. *
  4305. * License: www.highcharts.com/license
  4306. */
  4307. var addEvent = H.addEvent,
  4308. createElement = H.createElement,
  4309. objectEach = H.objectEach,
  4310. pick = H.pick,
  4311. wrap = H.wrap,
  4312. isString = H.isString,
  4313. isObject = H.isObject,
  4314. isArray = H.isArray,
  4315. indexFilter = /\d/g,
  4316. PREFIX = 'highcharts-',
  4317. DIV = 'div',
  4318. INPUT = 'input',
  4319. LABEL = 'label',
  4320. BUTTON = 'button',
  4321. SELECT = 'select',
  4322. OPTION = 'option',
  4323. SPAN = 'span',
  4324. UL = 'ul',
  4325. LI = 'li',
  4326. H3 = 'h3';
  4327. // onContainerMouseDown blocks internal popup events, due to e.preventDefault.
  4328. // Related issue #4606
  4329. wrap(H.Pointer.prototype, 'onContainerMouseDown', function (proceed, e) {
  4330. var popupClass = e.target && e.target.className;
  4331. // elements is not in popup
  4332. if (!(isString(popupClass) &&
  4333. popupClass.indexOf(PREFIX + 'popup-field') >= 0)
  4334. ) {
  4335. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  4336. }
  4337. });
  4338. H.Popup = function (parentDiv) {
  4339. this.init(parentDiv);
  4340. };
  4341. H.Popup.prototype = {
  4342. /*
  4343. * Initialize the popup. Create base div and add close button.
  4344. *
  4345. * @param {HTMLDOMElement} - container where popup should be placed
  4346. *
  4347. * @return {HTMLDOMElement} - return created popup's div
  4348. *
  4349. */
  4350. init: function (parentDiv) {
  4351. // create popup div
  4352. this.container = createElement(DIV, {
  4353. className: PREFIX + 'popup'
  4354. }, null, parentDiv);
  4355. this.lang = this.getLangpack();
  4356. // add close button
  4357. this.addCloseBtn();
  4358. },
  4359. /*
  4360. * Create HTML element and attach click event (close popup).
  4361. *
  4362. */
  4363. addCloseBtn: function () {
  4364. var _self = this,
  4365. closeBtn;
  4366. // create close popup btn
  4367. closeBtn = createElement(DIV, {
  4368. className: PREFIX + 'popup-close'
  4369. }, null, this.container);
  4370. ['click', 'touchstart'].forEach(function (eventName) {
  4371. addEvent(closeBtn, eventName, function () {
  4372. _self.closePopup();
  4373. });
  4374. });
  4375. },
  4376. /*
  4377. * Create two columns (divs) in HTML.
  4378. *
  4379. * @param {HTMLDOMElement} - container of columns
  4380. *
  4381. * @return {Object} - reference to two HTML columns
  4382. *
  4383. */
  4384. addColsContainer: function (container) {
  4385. var rhsCol,
  4386. lhsCol;
  4387. // left column
  4388. lhsCol = createElement(DIV, {
  4389. className: PREFIX + 'popup-lhs-col'
  4390. }, null, container);
  4391. // right column
  4392. rhsCol = createElement(DIV, {
  4393. className: PREFIX + 'popup-rhs-col'
  4394. }, null, container);
  4395. // wrapper content
  4396. createElement(DIV, {
  4397. className: PREFIX + 'popup-rhs-col-wrapper'
  4398. }, null, rhsCol);
  4399. return {
  4400. lhsCol: lhsCol,
  4401. rhsCol: rhsCol
  4402. };
  4403. },
  4404. /*
  4405. * Create input with label.
  4406. *
  4407. * @param {String} - chain of fields i.e params.styles.fontSize
  4408. * @param {String} - indicator type
  4409. * @param {HTMLDOMElement} - container where elements should be added
  4410. * @param {String} - dafault value of input i.e period value is 14,
  4411. * extracted from defaultOptions (ADD mode) or series options (EDIT mode)
  4412. *
  4413. */
  4414. addInput: function (option, type, parentDiv, value) {
  4415. var optionParamList = option.split('.'),
  4416. optionName = optionParamList[optionParamList.length - 1],
  4417. lang = this.lang,
  4418. inputName = PREFIX + type + '-' + optionName;
  4419. if (!inputName.match(indexFilter)) {
  4420. // add label
  4421. createElement(
  4422. LABEL, {
  4423. innerHTML: lang[optionName] || optionName,
  4424. htmlFor: inputName
  4425. },
  4426. null,
  4427. parentDiv
  4428. );
  4429. }
  4430. // add input
  4431. createElement(
  4432. INPUT,
  4433. {
  4434. name: inputName,
  4435. value: value[0],
  4436. type: value[1],
  4437. className: PREFIX + 'popup-field'
  4438. },
  4439. null,
  4440. parentDiv
  4441. ).setAttribute(PREFIX + 'data-name', option);
  4442. },
  4443. /*
  4444. * Create button.
  4445. *
  4446. * @param {HTMLDOMElement} - container where elements should be added
  4447. * @param {String} - text placed as button label
  4448. * @param {String} - add | edit | remove
  4449. * @param {Function} - on click callback
  4450. * @param {HTMLDOMElement} - container where inputs are generated
  4451. *
  4452. * @return {HTMLDOMElement} - html button
  4453. */
  4454. addButton: function (parentDiv, label, type, callback, fieldsDiv) {
  4455. var _self = this,
  4456. closePopup = this.closePopup,
  4457. getFields = this.getFields,
  4458. button;
  4459. button = createElement(BUTTON, {
  4460. innerHTML: label
  4461. }, null, parentDiv);
  4462. ['click', 'touchstart'].forEach(function (eventName) {
  4463. addEvent(button, eventName, function () {
  4464. closePopup.call(_self);
  4465. return callback(
  4466. getFields(fieldsDiv, type)
  4467. );
  4468. });
  4469. });
  4470. return button;
  4471. },
  4472. /*
  4473. * Get values from all inputs and create JSON.
  4474. *
  4475. * @param {HTMLDOMElement} - container where inputs are created
  4476. * @param {String} - add | edit | remove
  4477. *
  4478. * @return {Object} - fields
  4479. */
  4480. getFields: function (parentDiv, type) {
  4481. var inputList = parentDiv.querySelectorAll('input'),
  4482. optionSeries = '#' + PREFIX + 'select-series > option:checked',
  4483. optionVolume = '#' + PREFIX + 'select-volume > option:checked',
  4484. linkedTo = parentDiv.querySelectorAll(optionSeries)[0],
  4485. volumeTo = parentDiv.querySelectorAll(optionVolume)[0],
  4486. seriesId,
  4487. param,
  4488. fieldsOutput;
  4489. fieldsOutput = {
  4490. actionType: type,
  4491. linkedTo: linkedTo && linkedTo.getAttribute('value'),
  4492. fields: { }
  4493. };
  4494. inputList.forEach(function (input) {
  4495. param = input.getAttribute(PREFIX + 'data-name');
  4496. seriesId = input.getAttribute(PREFIX + 'data-series-id');
  4497. // params
  4498. if (seriesId) {
  4499. fieldsOutput.seriesId = input.value;
  4500. } else if (param) {
  4501. fieldsOutput.fields[param] = input.value;
  4502. } else {
  4503. // type like sma / ema
  4504. fieldsOutput.type = input.value;
  4505. }
  4506. });
  4507. if (volumeTo) {
  4508. fieldsOutput.fields['params.volumeSeriesID'] = volumeTo
  4509. .getAttribute('value');
  4510. }
  4511. return fieldsOutput;
  4512. },
  4513. /*
  4514. * Reset content of the current popup and show.
  4515. *
  4516. * @param {Chart} - chart
  4517. * @param {Function} - on click callback
  4518. *
  4519. * @return {Object} - fields
  4520. */
  4521. showPopup: function () {
  4522. var popupDiv = this.container,
  4523. toolbarClass = PREFIX + 'annotation-toolbar',
  4524. popupCloseBtn = popupDiv
  4525. .querySelectorAll('.' + PREFIX + 'popup-close')[0];
  4526. // reset content
  4527. popupDiv.innerHTML = '';
  4528. // reset toolbar styles if exists
  4529. if (popupDiv.className.indexOf(toolbarClass) >= 0) {
  4530. popupDiv.classList.remove(toolbarClass);
  4531. // reset toolbar inline styles
  4532. popupDiv.removeAttribute('style');
  4533. }
  4534. // add close button
  4535. popupDiv.appendChild(popupCloseBtn);
  4536. popupDiv.style.display = 'block';
  4537. },
  4538. /*
  4539. * Hide popup.
  4540. *
  4541. */
  4542. closePopup: function () {
  4543. this.popup.container.style.display = 'none';
  4544. },
  4545. /*
  4546. * Create content and show popup.
  4547. *
  4548. * @param {String} - type of popup i.e indicators
  4549. * @param {Chart} - chart
  4550. * @param {Object} - options
  4551. * @param {Function} - on click callback
  4552. *
  4553. */
  4554. showForm: function (type, chart, options, callback) {
  4555. this.popup = chart.navigationBindings.popup;
  4556. // show blank popup
  4557. this.showPopup();
  4558. // indicator form
  4559. if (type === 'indicators') {
  4560. this.indicators.addForm.call(this, chart, options, callback);
  4561. }
  4562. // annotation small toolbar
  4563. if (type === 'annotation-toolbar') {
  4564. this.annotations.addToolbar.call(this, chart, options, callback);
  4565. }
  4566. // annotation edit form
  4567. if (type === 'annotation-edit') {
  4568. this.annotations.addForm.call(this, chart, options, callback);
  4569. }
  4570. // flags form - add / edit
  4571. if (type === 'flag') {
  4572. this.annotations.addForm.call(this, chart, options, callback, true);
  4573. }
  4574. },
  4575. /*
  4576. * Return lang definitions for popup.
  4577. *
  4578. * @return {Object} - elements translations.
  4579. */
  4580. getLangpack: function () {
  4581. return H.getOptions().lang.navigation.popup;
  4582. },
  4583. annotations: {
  4584. /*
  4585. * Create annotation simple form. It contains two buttons
  4586. * (edit / remove) and text label.
  4587. *
  4588. * @param {Chart} - chart
  4589. * @param {Object} - options
  4590. * @param {Function} - on click callback
  4591. *
  4592. */
  4593. addToolbar: function (chart, options, callback) {
  4594. var _self = this,
  4595. lang = this.lang,
  4596. popupDiv = this.popup.container,
  4597. showForm = this.showForm,
  4598. toolbarClass = PREFIX + 'annotation-toolbar',
  4599. button;
  4600. // set small size
  4601. if (popupDiv.className.indexOf(toolbarClass) === -1) {
  4602. popupDiv.className += ' ' + toolbarClass;
  4603. }
  4604. // set position
  4605. popupDiv.style.top = chart.plotTop + 10 + 'px';
  4606. // create label
  4607. createElement(SPAN, {
  4608. innerHTML: pick(
  4609. // Advanced annotations:
  4610. lang[options.langKey] || options.langKey,
  4611. // Basic shapes:
  4612. options.shapes && options.shapes[0].type
  4613. )
  4614. }, null, popupDiv);
  4615. // add buttons
  4616. button = this.addButton(
  4617. popupDiv,
  4618. lang.removeButton || 'remove',
  4619. 'remove',
  4620. callback,
  4621. popupDiv
  4622. );
  4623. button.className += ' ' + PREFIX + 'annotation-remove-button';
  4624. button = this.addButton(
  4625. popupDiv,
  4626. lang.editButton || 'edit',
  4627. 'edit',
  4628. function () {
  4629. showForm.call(
  4630. _self,
  4631. 'annotation-edit',
  4632. chart,
  4633. options,
  4634. callback
  4635. );
  4636. },
  4637. popupDiv
  4638. );
  4639. button.className += ' ' + PREFIX + 'annotation-edit-button';
  4640. },
  4641. /*
  4642. * Create annotation simple form.
  4643. * It contains fields with param names.
  4644. *
  4645. * @param {Chart} - chart
  4646. * @param {Object} - options
  4647. * @param {Function} - on click callback
  4648. * @param {Boolean} - if it is a form declared for init annotation
  4649. *
  4650. */
  4651. addForm: function (chart, options, callback, isInit) {
  4652. var popupDiv = this.popup.container,
  4653. lang = this.lang,
  4654. bottomRow,
  4655. lhsCol;
  4656. // create title of annotations
  4657. lhsCol = createElement('h2', {
  4658. innerHTML: lang[options.langKey] || options.langKey,
  4659. className: PREFIX + 'popup-main-title'
  4660. }, null, popupDiv);
  4661. // left column
  4662. lhsCol = createElement(DIV, {
  4663. className: PREFIX + 'popup-lhs-col ' + PREFIX + 'popup-lhs-full'
  4664. }, null, popupDiv);
  4665. bottomRow = createElement(DIV, {
  4666. className: PREFIX + 'popup-bottom-row'
  4667. }, null, popupDiv);
  4668. this.annotations.addFormFields.call(
  4669. this,
  4670. lhsCol,
  4671. chart,
  4672. '',
  4673. options,
  4674. [],
  4675. true
  4676. );
  4677. this.addButton(
  4678. bottomRow,
  4679. isInit ?
  4680. (lang.addButton || 'add') :
  4681. (lang.saveButton || 'save'),
  4682. isInit ? 'add' : 'save',
  4683. callback,
  4684. popupDiv
  4685. );
  4686. },
  4687. /*
  4688. * Create annotation's form fields.
  4689. *
  4690. * @param {HTMLDOMElement} - div where inputs are placed
  4691. * @param {Chart} - chart
  4692. * @param {String} - name of parent to create chain of names
  4693. * @param {Object} - options
  4694. * @param {Array} - storage - array where all items are stored
  4695. * @param {Boolean} - isRoot - recursive flag for root
  4696. *
  4697. */
  4698. addFormFields: function (
  4699. parentDiv,
  4700. chart,
  4701. parentNode,
  4702. options,
  4703. storage,
  4704. isRoot
  4705. ) {
  4706. var _self = this,
  4707. addFormFields = this.annotations.addFormFields,
  4708. addInput = this.addInput,
  4709. lang = this.lang,
  4710. parentFullName,
  4711. titleName;
  4712. objectEach(options, function (value, option) {
  4713. // create name like params.styles.fontSize
  4714. parentFullName = parentNode !== '' ?
  4715. parentNode + '.' + option : option;
  4716. if (isObject(value)) {
  4717. if (
  4718. // value is object of options
  4719. !isArray(value) ||
  4720. // array of objects with params. i.e labels in Fibonacci
  4721. (isArray(value) && isObject(value[0]))
  4722. ) {
  4723. titleName = lang[option] || option;
  4724. if (!titleName.match(indexFilter)) {
  4725. storage.push([
  4726. true,
  4727. titleName,
  4728. parentDiv
  4729. ]);
  4730. }
  4731. addFormFields.call(
  4732. _self,
  4733. parentDiv,
  4734. chart,
  4735. parentFullName,
  4736. value,
  4737. storage,
  4738. false
  4739. );
  4740. } else {
  4741. storage.push([
  4742. _self,
  4743. parentFullName,
  4744. 'annotation',
  4745. parentDiv,
  4746. value
  4747. ]);
  4748. }
  4749. }
  4750. });
  4751. if (isRoot) {
  4752. storage = storage.sort(function (a) {
  4753. return a[1].match(/format/g) ? -1 : 1;
  4754. });
  4755. storage.forEach(function (genInput) {
  4756. if (genInput[0] === true) {
  4757. createElement(SPAN, {
  4758. className: PREFIX + 'annotation-title',
  4759. innerHTML: genInput[1]
  4760. }, null, genInput[2]);
  4761. } else {
  4762. addInput.apply(genInput[0], genInput.splice(1));
  4763. }
  4764. });
  4765. }
  4766. }
  4767. },
  4768. indicators: {
  4769. /*
  4770. * Create indicator's form. It contains two tabs (ADD and EDIT) with
  4771. * content.
  4772. *
  4773. * @param {Chart} - chart
  4774. * @param {Object} - options
  4775. * @param {Function} - on click callback
  4776. *
  4777. */
  4778. addForm: function (chart, options, callback) {
  4779. var tabsContainers,
  4780. indicators = this.indicators,
  4781. lang = this.lang,
  4782. buttonParentDiv;
  4783. // add tabs
  4784. this.tabs.init.call(this, chart);
  4785. // get all tabs content divs
  4786. tabsContainers = this.popup.container
  4787. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  4788. // ADD tab
  4789. this.addColsContainer(tabsContainers[0]);
  4790. indicators.addIndicatorList.call(
  4791. this,
  4792. chart,
  4793. tabsContainers[0],
  4794. 'add'
  4795. );
  4796. buttonParentDiv = tabsContainers[0]
  4797. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  4798. this.addButton(
  4799. buttonParentDiv,
  4800. lang.addButton || 'add',
  4801. 'add',
  4802. callback,
  4803. buttonParentDiv
  4804. );
  4805. // EDIT tab
  4806. this.addColsContainer(tabsContainers[1]);
  4807. indicators.addIndicatorList.call(
  4808. this,
  4809. chart,
  4810. tabsContainers[1],
  4811. 'edit'
  4812. );
  4813. buttonParentDiv = tabsContainers[1]
  4814. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  4815. this.addButton(
  4816. buttonParentDiv,
  4817. lang.saveButton || 'save',
  4818. 'edit',
  4819. callback,
  4820. buttonParentDiv
  4821. );
  4822. this.addButton(
  4823. buttonParentDiv,
  4824. lang.removeButton || 'remove',
  4825. 'remove',
  4826. callback,
  4827. buttonParentDiv
  4828. );
  4829. },
  4830. /*
  4831. * Create HTML list of all indicators (ADD mode) or added indicators
  4832. * (EDIT mode).
  4833. *
  4834. * @param {Chart} - chart
  4835. * @param {HTMLDOMElement} - container where list is added
  4836. * @param {String} - 'edit' or 'add' mode
  4837. *
  4838. */
  4839. addIndicatorList: function (chart, parentDiv, listType) {
  4840. var _self = this,
  4841. lhsCol = parentDiv
  4842. .querySelectorAll('.' + PREFIX + 'popup-lhs-col')[0],
  4843. rhsCol = parentDiv
  4844. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0],
  4845. defaultOptions = H.getOptions(),
  4846. isEdit = listType === 'edit',
  4847. series = isEdit ? chart.series : // EDIT mode
  4848. defaultOptions.plotOptions, // ADD mode
  4849. addFormFields = this.indicators.addFormFields,
  4850. rhsColWrapper,
  4851. indicatorList,
  4852. item;
  4853. // create wrapper for list
  4854. indicatorList = createElement(UL, {
  4855. className: PREFIX + 'indicator-list'
  4856. }, null, lhsCol);
  4857. rhsColWrapper = rhsCol
  4858. .querySelectorAll('.' + PREFIX + 'popup-rhs-col-wrapper')[0];
  4859. objectEach(series, function (serie, value) {
  4860. var seriesOptions = serie.options;
  4861. if (
  4862. serie.params ||
  4863. seriesOptions && seriesOptions.params
  4864. ) {
  4865. var indicatorNameType = _self.indicators
  4866. .getNameType(serie, value),
  4867. indicatorType = indicatorNameType.type;
  4868. item = createElement(LI, {
  4869. className: PREFIX + 'indicator-list',
  4870. innerHTML: indicatorNameType.name
  4871. }, null, indicatorList);
  4872. ['click', 'touchstart'].forEach(function (eventName) {
  4873. addEvent(item, eventName, function () {
  4874. addFormFields.call(
  4875. _self,
  4876. chart,
  4877. isEdit ? serie : series[indicatorType],
  4878. indicatorNameType.type,
  4879. rhsColWrapper
  4880. );
  4881. // add hidden input with series.id
  4882. if (isEdit && serie.options) {
  4883. createElement(INPUT, {
  4884. type: 'hidden',
  4885. name: PREFIX + 'id-' + indicatorType,
  4886. value: serie.options.id
  4887. }, null, rhsColWrapper)
  4888. .setAttribute(
  4889. PREFIX + 'data-series-id',
  4890. serie.options.id
  4891. );
  4892. }
  4893. });
  4894. });
  4895. }
  4896. });
  4897. // select first item from the list
  4898. if (indicatorList.childNodes.length > 0) {
  4899. indicatorList.childNodes[0].click();
  4900. }
  4901. },
  4902. /*
  4903. * Extract full name and type of requested indicator.
  4904. *
  4905. * @param {Series} - series which name is needed.
  4906. * (EDIT mode - defaultOptions.series, ADD mode - indicator series).
  4907. * @param {String} - indicator type like: sma, ema, etc.
  4908. *
  4909. * @return {Object} - series name and type like: sma, ema, etc.
  4910. *
  4911. */
  4912. getNameType: function (series, type) {
  4913. var options = series.options,
  4914. seriesTypes = H.seriesTypes,
  4915. // add mode
  4916. seriesName = seriesTypes[type] &&
  4917. seriesTypes[type].prototype.nameBase || type.toUpperCase(),
  4918. seriesType = type;
  4919. // edit
  4920. if (options && options.type) {
  4921. seriesType = series.options.type;
  4922. seriesName = series.name;
  4923. }
  4924. return {
  4925. name: seriesName,
  4926. type: seriesType
  4927. };
  4928. },
  4929. /*
  4930. * List all series with unique ID. Its mandatory for indicators to set
  4931. * correct linking.
  4932. *
  4933. * @param {String} - indicator type like: sma, ema, etc.
  4934. * @param {String} - type of select i.e series or volume.
  4935. * @param {Chart} - chart
  4936. * @param {HTMLDOMElement} - element where created HTML list is added
  4937. *
  4938. */
  4939. listAllSeries: function (type, optionName, chart, parentDiv) {
  4940. var selectName = PREFIX + optionName + '-type-' + type,
  4941. lang = this.lang,
  4942. selectBox,
  4943. seriesOptions;
  4944. createElement(
  4945. LABEL, {
  4946. innerHTML: lang[optionName] || optionName,
  4947. htmlFor: selectName
  4948. },
  4949. null,
  4950. parentDiv
  4951. );
  4952. // select type
  4953. selectBox = createElement(
  4954. SELECT,
  4955. {
  4956. name: selectName,
  4957. className: PREFIX + 'popup-field'
  4958. },
  4959. null,
  4960. parentDiv
  4961. );
  4962. selectBox.setAttribute('id', PREFIX + 'select-' + optionName);
  4963. // list all series which have id - mandatory for creating indicator
  4964. chart.series.forEach(function (serie) {
  4965. seriesOptions = serie.options;
  4966. if (
  4967. !seriesOptions.params &&
  4968. seriesOptions.id &&
  4969. seriesOptions.id !== PREFIX + 'navigator-series'
  4970. ) {
  4971. createElement(
  4972. OPTION,
  4973. {
  4974. innerHTML: seriesOptions.name || seriesOptions.id,
  4975. value: seriesOptions.id
  4976. },
  4977. null,
  4978. selectBox
  4979. );
  4980. }
  4981. });
  4982. },
  4983. /*
  4984. * Create typical inputs for chosen indicator. Fields are extracted from
  4985. * defaultOptions (ADD mode) or current indicator (ADD mode). Two extra
  4986. * fields are added:
  4987. * - hidden input - contains indicator type (required for callback)
  4988. * - select - list of series which can be linked with indicator
  4989. *
  4990. * @param {Chart} - chart
  4991. * @param {Series} - indicator
  4992. * @param {String} - indicator type like: sma, ema, etc.
  4993. * @param {HTMLDOMElement} - element where created HTML list is added
  4994. *
  4995. */
  4996. addFormFields: function (chart, series, seriesType, rhsColWrapper) {
  4997. var fields = series.params || series.options.params,
  4998. getNameType = this.indicators.getNameType;
  4999. // reset current content
  5000. rhsColWrapper.innerHTML = '';
  5001. // create title (indicator name in the right column)
  5002. createElement(
  5003. H3,
  5004. {
  5005. className: PREFIX + 'indicator-title',
  5006. innerHTML: getNameType(series, seriesType).name
  5007. },
  5008. null,
  5009. rhsColWrapper
  5010. );
  5011. // input type
  5012. createElement(
  5013. INPUT,
  5014. {
  5015. type: 'hidden',
  5016. name: PREFIX + 'type-' + seriesType,
  5017. value: seriesType
  5018. },
  5019. null,
  5020. rhsColWrapper
  5021. );
  5022. // list all series with id
  5023. this.indicators.listAllSeries.call(
  5024. this,
  5025. seriesType,
  5026. 'series',
  5027. chart,
  5028. rhsColWrapper
  5029. );
  5030. if (fields.volumeSeriesID) {
  5031. this.indicators.listAllSeries.call(
  5032. this,
  5033. seriesType,
  5034. 'volume',
  5035. chart,
  5036. rhsColWrapper
  5037. );
  5038. }
  5039. // add param fields
  5040. this.indicators.addParamInputs.call(
  5041. this,
  5042. chart,
  5043. 'params',
  5044. fields,
  5045. seriesType,
  5046. rhsColWrapper
  5047. );
  5048. },
  5049. /*
  5050. * Recurent function which lists all fields, from params object and
  5051. * create them as inputs. Each input has unique `data-name` attribute,
  5052. * which keeps chain of fields i.e params.styles.fontSize.
  5053. *
  5054. * @param {Chart} - chart
  5055. * @param {String} - name of parent to create chain of names
  5056. * @param {Series} - fields - params which are based for input create
  5057. * @param {String} - indicator type like: sma, ema, etc.
  5058. * @param {HTMLDOMElement} - element where created HTML list is added
  5059. *
  5060. */
  5061. addParamInputs: function (chart, parentNode, fields, type, parentDiv) {
  5062. var _self = this,
  5063. addParamInputs = this.indicators.addParamInputs,
  5064. addInput = this.addInput,
  5065. parentFullName;
  5066. objectEach(fields, function (value, fieldName) {
  5067. // create name like params.styles.fontSize
  5068. parentFullName = parentNode + '.' + fieldName;
  5069. if (isObject(value)) {
  5070. addParamInputs.call(
  5071. _self,
  5072. chart,
  5073. parentFullName,
  5074. value,
  5075. type,
  5076. parentDiv
  5077. );
  5078. } else if (
  5079. // skip volume field which is created by addFormFields
  5080. parentFullName !== 'params.volumeSeriesID'
  5081. ) {
  5082. addInput.call(
  5083. _self,
  5084. parentFullName,
  5085. type,
  5086. parentDiv,
  5087. [value, 'text'] // all inputs are text type
  5088. );
  5089. }
  5090. });
  5091. },
  5092. /*
  5093. * Get amount of indicators added to chart.
  5094. *
  5095. * @return {Number} - Amount of indicators
  5096. */
  5097. getAmount: function () {
  5098. var series = this.series,
  5099. counter = 0;
  5100. objectEach(series, function (serie) {
  5101. var seriesOptions = serie.options;
  5102. if (
  5103. serie.params ||
  5104. seriesOptions && seriesOptions.params
  5105. ) {
  5106. counter++;
  5107. }
  5108. });
  5109. return counter;
  5110. }
  5111. },
  5112. tabs: {
  5113. /*
  5114. * Init tabs. Create tab menu items, tabs containers
  5115. *
  5116. * @param {Chart} - reference to current chart
  5117. *
  5118. */
  5119. init: function (chart) {
  5120. var tabs = this.tabs,
  5121. indicatorsCount = this.indicators.getAmount.call(chart),
  5122. firstTab; // run by default
  5123. // create menu items
  5124. firstTab = tabs.addMenuItem.call(this, 'add');
  5125. tabs.addMenuItem.call(this, 'edit', indicatorsCount);
  5126. // create tabs containers
  5127. tabs.addContentItem.call(this, 'add');
  5128. tabs.addContentItem.call(this, 'edit');
  5129. tabs.switchTabs.call(this, indicatorsCount);
  5130. // activate first tab
  5131. tabs.selectTab.call(this, firstTab, 0);
  5132. },
  5133. /*
  5134. * Create tab menu item
  5135. *
  5136. * @param {String} - `add` or `edit`
  5137. * @param {Number} - Disable tab when 0
  5138. *
  5139. * @return {HTMLDOMElement} - created HTML tab-menu element
  5140. */
  5141. addMenuItem: function (tabName, disableTab) {
  5142. var popupDiv = this.popup.container,
  5143. className = PREFIX + 'tab-item',
  5144. lang = this.lang,
  5145. menuItem;
  5146. if (disableTab === 0) {
  5147. className += ' ' + PREFIX + 'tab-disabled';
  5148. }
  5149. // tab 1
  5150. menuItem = createElement(
  5151. SPAN,
  5152. {
  5153. innerHTML: lang[tabName + 'Button'] || tabName,
  5154. className: className
  5155. },
  5156. null,
  5157. popupDiv
  5158. );
  5159. menuItem.setAttribute(PREFIX + 'data-tab-type', tabName);
  5160. return menuItem;
  5161. },
  5162. /*
  5163. * Create tab content
  5164. *
  5165. * @return {HTMLDOMElement} - created HTML tab-content element
  5166. *
  5167. */
  5168. addContentItem: function () {
  5169. var popupDiv = this.popup.container;
  5170. return createElement(
  5171. DIV,
  5172. {
  5173. className: PREFIX + 'tab-item-content'
  5174. },
  5175. null,
  5176. popupDiv
  5177. );
  5178. },
  5179. /*
  5180. * Add click event to each tab
  5181. *
  5182. * @param {Number} - Disable tab when 0
  5183. *
  5184. */
  5185. switchTabs: function (disableTab) {
  5186. var _self = this,
  5187. popupDiv = this.popup.container,
  5188. tabs = popupDiv.querySelectorAll('.' + PREFIX + 'tab-item'),
  5189. dataParam;
  5190. tabs.forEach(function (tab, i) {
  5191. dataParam = tab.getAttribute(PREFIX + 'data-tab-type');
  5192. if (dataParam === 'edit' && disableTab === 0) {
  5193. return;
  5194. }
  5195. ['click', 'touchstart'].forEach(function (eventName) {
  5196. addEvent(tab, eventName, function () {
  5197. // reset class on other elements
  5198. _self.tabs.deselectAll.call(_self);
  5199. _self.tabs.selectTab.call(_self, this, i);
  5200. });
  5201. });
  5202. });
  5203. },
  5204. /*
  5205. * Set tab as visible
  5206. *
  5207. * @param {HTMLDOMElement} - current tab
  5208. * @param {Number} - Index of tab in menu
  5209. *
  5210. */
  5211. selectTab: function (tab, index) {
  5212. var allTabs = this.popup.container
  5213. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  5214. tab.className += ' ' + PREFIX + 'tab-item-active';
  5215. allTabs[index].className += ' ' + PREFIX + 'tab-item-show';
  5216. },
  5217. /*
  5218. * Set all tabs as invisible.
  5219. *
  5220. */
  5221. deselectAll: function () {
  5222. var popupDiv = this.popup.container,
  5223. tabs = popupDiv
  5224. .querySelectorAll('.' + PREFIX + 'tab-item'),
  5225. tabsContent = popupDiv
  5226. .querySelectorAll('.' + PREFIX + 'tab-item-content'),
  5227. i;
  5228. for (i = 0; i < tabs.length; i++) {
  5229. tabs[i].classList.remove(PREFIX + 'tab-item-active');
  5230. tabsContent[i].classList.remove(PREFIX + 'tab-item-show');
  5231. }
  5232. }
  5233. }
  5234. };
  5235. addEvent(H.NavigationBindings, 'showPopup', function (config) {
  5236. if (!this.popup) {
  5237. // Add popup to main container
  5238. this.popup = new H.Popup(this.chart.container);
  5239. }
  5240. this.popup.showForm(
  5241. config.formType,
  5242. this.chart,
  5243. config.options,
  5244. config.onSubmit
  5245. );
  5246. });
  5247. addEvent(H.NavigationBindings, 'closePopup', function () {
  5248. if (this.popup) {
  5249. this.popup.closePopup();
  5250. }
  5251. });
  5252. }(Highcharts));
  5253. return (function () {
  5254. }());
  5255. }));