SvgRenderer.js 162 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109
  1. /* *
  2. *
  3. * (c) 2010-2019 Torstein Honsi
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * */
  8. /**
  9. * The horizontal alignment of an element.
  10. *
  11. * @typedef {"center"|"left"|"right"} Highcharts.AlignType
  12. */
  13. /**
  14. * Options to align the element relative to the chart or another box.
  15. *
  16. * @interface Highcharts.AlignObject
  17. *//**
  18. * Horizontal alignment. Can be one of `left`, `center` and `right`.
  19. *
  20. * @name Highcharts.AlignObject#align
  21. * @type {Highcharts.AlignType|undefined}
  22. *
  23. * @default left
  24. *//**
  25. * Vertical alignment. Can be one of `top`, `middle` and `bottom`.
  26. *
  27. * @name Highcharts.AlignObject#verticalAlign
  28. * @type {Highcharts.VerticalAlignType|undefined}
  29. *
  30. * @default top
  31. *//**
  32. * Horizontal pixel offset from alignment.
  33. *
  34. * @name Highcharts.AlignObject#x
  35. * @type {number|undefined}
  36. *
  37. * @default 0
  38. *//**
  39. * Vertical pixel offset from alignment.
  40. *
  41. * @name Highcharts.AlignObject#y
  42. * @type {number|undefined}
  43. *
  44. * @default 0
  45. *//**
  46. * Use the `transform` attribute with translateX and translateY custom
  47. * attributes to align this elements rather than `x` and `y` attributes.
  48. *
  49. * @name Highcharts.AlignObject#alignByTranslate
  50. * @type {boolean|undefined}
  51. *
  52. * @default false
  53. */
  54. /**
  55. * Bounding box of an element.
  56. *
  57. * @interface Highcharts.BBoxObject
  58. *//**
  59. * Height of the bounding box.
  60. *
  61. * @name Highcharts.BBoxObject#height
  62. * @type {number}
  63. *//**
  64. * Width of the bounding box.
  65. *
  66. * @name Highcharts.BBoxObject#width
  67. * @type {number}
  68. *//**
  69. * Horizontal position of the bounding box.
  70. *
  71. * @name Highcharts.BBoxObject#x
  72. * @type {number}
  73. *//**
  74. * Vertical position of the bounding box.
  75. *
  76. * @name Highcharts.BBoxObject#y
  77. * @type {number}
  78. */
  79. /**
  80. * A clipping rectangle that can be applied to one or more {@link SVGElement}
  81. * instances. It is instanciated with the {@link SVGRenderer#clipRect} function
  82. * and applied with the {@link SVGElement#clip} function.
  83. *
  84. * @example
  85. * var circle = renderer.circle(100, 100, 100)
  86. * .attr({ fill: 'red' })
  87. * .add();
  88. * var clipRect = renderer.clipRect(100, 100, 100, 100);
  89. *
  90. * // Leave only the lower right quarter visible
  91. * circle.clip(clipRect);
  92. *
  93. * @typedef {Highcharts.SVGElement} Highcharts.ClipRectElement
  94. */
  95. /**
  96. * The font metrics.
  97. *
  98. * @interface Highcharts.FontMetricsObject
  99. *//**
  100. * The baseline relative to the top of the box.
  101. *
  102. * @name Highcharts.FontMetricsObject#b
  103. * @type {number}
  104. *//**
  105. * The line height.
  106. *
  107. * @name Highcharts.FontMetricsObject#h
  108. * @type {number}
  109. *//**
  110. * The font size.
  111. *
  112. * @name Highcharts.FontMetricsObject#f
  113. * @type {number}
  114. */
  115. /**
  116. * Gradient options instead of a solid color.
  117. *
  118. * @example
  119. * // Linear gradient used as a color option
  120. * color: {
  121. * linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
  122. * stops: [
  123. * [0, '#003399'], // start
  124. * [0.5, '#ffffff'], // middle
  125. * [1, '#3366AA'] // end
  126. * ]
  127. * }
  128. * }
  129. *
  130. * @interface Highcharts.GradientColorObject
  131. *//**
  132. * Holds an object that defines the start position and the end position relative
  133. * to the shape.
  134. * @name Highcharts.GradientColorObject#linearGradient
  135. * @type {Highcharts.LinearGradientColorObject|undefined}
  136. *//**
  137. * Holds an object that defines the center position and the radius.
  138. * @name Highcharts.GradientColorObject#radialGradient
  139. * @type {Highcharts.RadialGradientColorObject|undefined}
  140. *//**
  141. * The first item in each tuple is the position in the gradient, where 0 is the
  142. * start of the gradient and 1 is the end of the gradient. Multiple stops can be
  143. * applied. The second item is the color for each stop. This color can also be
  144. * given in the rgba format.
  145. * @name Highcharts.GradientColorObject#stops
  146. * @type {Array<Array<number,Highcharts.ColorString>>|undefined}
  147. */
  148. /**
  149. * Defines the start position and the end position for a gradient relative
  150. * to the shape. Start position (x1, y1) and end position (x2, y2) are relative
  151. * to the shape, where 0 means top/left and 1 is bottom/right.
  152. *
  153. * @interface Highcharts.LinearGradientColorObject
  154. *//**
  155. * Start horizontal position of the gradient. Float ranges 0-1.
  156. * @name Highcharts.LinearGradientColorObject#x1
  157. * @type {number}
  158. *//**
  159. * End horizontal position of the gradient. Float ranges 0-1.
  160. * @name Highcharts.LinearGradientColorObject#x2
  161. * @type {number}
  162. *//**
  163. * Start vertical position of the gradient. Float ranges 0-1.
  164. * @name Highcharts.LinearGradientColorObject#y1
  165. * @type {number}
  166. *//**
  167. * End vertical position of the gradient. Float ranges 0-1.
  168. * @name Highcharts.LinearGradientColorObject#y2
  169. * @type {number}
  170. */
  171. /**
  172. * An object containing `x` and `y` properties for the position of an element.
  173. *
  174. * @interface Highcharts.PositionObject
  175. *//**
  176. * X position of the element.
  177. * @name Highcharts.PositionObject#x
  178. * @type {number}
  179. *//**
  180. * Y position of the element.
  181. * @name Highcharts.PositionObject#y
  182. * @type {number}
  183. */
  184. /**
  185. * Defines the center position and the radius for a gradient.
  186. *
  187. * @interface Highcharts.RadialGradientColorObject
  188. *//**
  189. * Center horizontal position relative to the shape. Float ranges 0-1.
  190. * @name Highcharts.RadialGradientColorObject#cx
  191. * @type {number}
  192. *//**
  193. * Center vertical position relative to the shape. Float ranges 0-1.
  194. * @name Highcharts.RadialGradientColorObject#cy
  195. * @type {number}
  196. *//**
  197. * Radius relative to the shape. Float ranges 0-1.
  198. * @name Highcharts.RadialGradientColorObject#r
  199. * @type {number}
  200. */
  201. /**
  202. * A rectangle.
  203. *
  204. * @interface Highcharts.RectangleObject
  205. *//**
  206. * Height of the rectangle.
  207. * @name Highcharts.RectangleObject#height
  208. * @type {number}
  209. *//**
  210. * Width of the rectangle.
  211. * @name Highcharts.RectangleObject#width
  212. * @type {number}
  213. *//**
  214. * Horizontal position of the rectangle.
  215. * @name Highcharts.RectangleObject#x
  216. * @type {number}
  217. *//**
  218. * Vertical position of the rectangle.
  219. * @name Highcharts.RectangleObject#y
  220. * @type {number}
  221. */
  222. /**
  223. * The shadow options.
  224. *
  225. * @interface Highcharts.ShadowOptionsObject
  226. *//**
  227. * The shadow color.
  228. * @name Highcharts.ShadowOptionsObject#color
  229. * @type {string|undefined}
  230. * @default #000000
  231. *//**
  232. * The horizontal offset from the element.
  233. *
  234. * @name Highcharts.ShadowOptionsObject#offsetX
  235. * @type {number|undefined}
  236. * @default 1
  237. *//**
  238. * The vertical offset from the element.
  239. * @name Highcharts.ShadowOptionsObject#offsetY
  240. * @type {number|undefined}
  241. * @default 1
  242. *//**
  243. * The shadow opacity.
  244. *
  245. * @name Highcharts.ShadowOptionsObject#opacity
  246. * @type {number|undefined}
  247. * @default 0.15
  248. *//**
  249. * The shadow width or distance from the element.
  250. * @name Highcharts.ShadowOptionsObject#width
  251. * @type {number|undefined}
  252. * @default 3
  253. */
  254. /**
  255. * Serialized form of an SVG definition, including children. Some key
  256. * property names are reserved: tagName, textContent, and children.
  257. *
  258. * @interface Highcharts.SVGDefinitionObject
  259. *//**
  260. * @name Highcharts.SVGDefinitionObject#[key:string]
  261. * @type {number|string|Array<Highcharts.SVGDefinitionObject>|undefined}
  262. *//**
  263. * @name Highcharts.SVGDefinitionObject#children
  264. * @type {Array<Highcharts.SVGDefinitionObject>|undefined}
  265. *//**
  266. * @name Highcharts.SVGDefinitionObject#tagName
  267. * @type {string|undefined}
  268. *//**
  269. * @name Highcharts.SVGDefinitionObject#textContent
  270. * @type {string|undefined}
  271. */
  272. /**
  273. * An extendable collection of functions for defining symbol paths.
  274. *
  275. * @typedef Highcharts.SymbolDictionary
  276. *
  277. * @property {Function|undefined} [key:Highcharts.SymbolKey]
  278. */
  279. /**
  280. * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`,
  281. * `triangle`, `triangle-down`. Symbols are used internally for point
  282. * markers, button and label borders and backgrounds, or custom shapes.
  283. * Extendable by adding to {@link SVGRenderer#symbols}.
  284. *
  285. * @typedef {string} Highcharts.SymbolKey
  286. * @validvalue ["arc", "callout", "circle", "diamond", "square", "triangle",
  287. * "triangle-down"]
  288. */
  289. /**
  290. * Additional options, depending on the actual symbol drawn.
  291. *
  292. * @interface Highcharts.SymbolOptionsObject
  293. *//**
  294. * The anchor X position for the `callout` symbol. This is where the chevron
  295. * points to.
  296. *
  297. * @name Highcharts.SymbolOptionsObject#anchorX
  298. * @type {number}
  299. *//**
  300. * The anchor Y position for the `callout` symbol. This is where the chevron
  301. * points to.
  302. *
  303. * @name Highcharts.SymbolOptionsObject#anchorY
  304. * @type {number}
  305. *//**
  306. * The end angle of an `arc` symbol.
  307. *
  308. * @name Highcharts.SymbolOptionsObject#end
  309. * @type {number}
  310. *//**
  311. * Whether to draw `arc` symbol open or closed.
  312. *
  313. * @name Highcharts.SymbolOptionsObject#open
  314. * @type {boolean}
  315. *//**
  316. * The radius of an `arc` symbol, or the border radius for the `callout` symbol.
  317. *
  318. * @name Highcharts.SymbolOptionsObject#r
  319. * @type {number}
  320. *//**
  321. * The start angle of an `arc` symbol.
  322. *
  323. * @name Highcharts.SymbolOptionsObject#start
  324. * @type {number}
  325. */
  326. /**
  327. * The vertical alignment of an element.
  328. *
  329. * @typedef {"bottom"|"middle"|"top"} Highcharts.VerticalAlignType
  330. */
  331. 'use strict';
  332. import H from './Globals.js';
  333. import './Utilities.js';
  334. import './Color.js';
  335. var SVGElement,
  336. SVGRenderer,
  337. addEvent = H.addEvent,
  338. animate = H.animate,
  339. attr = H.attr,
  340. charts = H.charts,
  341. color = H.color,
  342. css = H.css,
  343. createElement = H.createElement,
  344. defined = H.defined,
  345. deg2rad = H.deg2rad,
  346. destroyObjectProperties = H.destroyObjectProperties,
  347. doc = H.doc,
  348. extend = H.extend,
  349. erase = H.erase,
  350. hasTouch = H.hasTouch,
  351. isArray = H.isArray,
  352. isFirefox = H.isFirefox,
  353. isMS = H.isMS,
  354. isObject = H.isObject,
  355. isString = H.isString,
  356. isWebKit = H.isWebKit,
  357. merge = H.merge,
  358. noop = H.noop,
  359. objectEach = H.objectEach,
  360. pick = H.pick,
  361. pInt = H.pInt,
  362. removeEvent = H.removeEvent,
  363. splat = H.splat,
  364. stop = H.stop,
  365. svg = H.svg,
  366. SVG_NS = H.SVG_NS,
  367. symbolSizes = H.symbolSizes,
  368. win = H.win;
  369. /**
  370. * The SVGElement prototype is a JavaScript wrapper for SVG elements used in the
  371. * rendering layer of Highcharts. Combined with the {@link
  372. * Highcharts.SVGRenderer} object, these prototypes allow freeform annotation
  373. * in the charts or even in HTML pages without instanciating a chart. The
  374. * SVGElement can also wrap HTML labels, when `text` or `label` elements are
  375. * created with the `useHTML` parameter.
  376. *
  377. * The SVGElement instances are created through factory functions on the {@link
  378. * Highcharts.SVGRenderer} object, like {@link Highcharts.SVGRenderer#rect|
  379. * rect}, {@link Highcharts.SVGRenderer#path|path}, {@link
  380. * Highcharts.SVGRenderer#text|text}, {@link Highcharts.SVGRenderer#label|
  381. * label}, {@link Highcharts.SVGRenderer#g|g} and more.
  382. *
  383. * @class
  384. * @name Highcharts.SVGElement
  385. */
  386. SVGElement = H.SVGElement = function () {
  387. return this;
  388. };
  389. extend(SVGElement.prototype, /** @lends Highcharts.SVGElement.prototype */ {
  390. // Default base for animation
  391. opacity: 1,
  392. SVG_NS: SVG_NS,
  393. /**
  394. * For labels, these CSS properties are applied to the `text` node directly.
  395. *
  396. * @private
  397. * @name Highcharts.SVGElement#textProps
  398. * @type {Array<string>}
  399. */
  400. textProps: ['direction', 'fontSize', 'fontWeight', 'fontFamily',
  401. 'fontStyle', 'color', 'lineHeight', 'width', 'textAlign',
  402. 'textDecoration', 'textOverflow', 'textOutline', 'cursor'],
  403. /**
  404. * Initialize the SVG element. This function only exists to make the
  405. * initiation process overridable. It should not be called directly.
  406. *
  407. * @function Highcharts.SVGElement#init
  408. *
  409. * @param {Highcharts.SVGRenderer} renderer
  410. * The SVGRenderer instance to initialize to.
  411. *
  412. * @param {string} nodeName
  413. * The SVG node name.
  414. */
  415. init: function (renderer, nodeName) {
  416. /**
  417. * The primary DOM node. Each `SVGElement` instance wraps a main DOM
  418. * node, but may also represent more nodes.
  419. *
  420. * @name Highcharts.SVGElement#element
  421. * @type {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement}
  422. */
  423. this.element = nodeName === 'span' ?
  424. createElement(nodeName) :
  425. doc.createElementNS(this.SVG_NS, nodeName);
  426. /**
  427. * The renderer that the SVGElement belongs to.
  428. *
  429. * @name Highcharts.SVGElement#renderer
  430. * @type {Highcharts.SVGRenderer}
  431. */
  432. this.renderer = renderer;
  433. H.fireEvent(this, 'afterInit');
  434. },
  435. /**
  436. * Animate to given attributes or CSS properties.
  437. *
  438. * @sample highcharts/members/element-on/
  439. * Setting some attributes by animation
  440. *
  441. * @function Highcharts.SVGElement#animate
  442. *
  443. * @param {Highcharts.SVGAttributes} params
  444. * SVG attributes or CSS to animate.
  445. *
  446. * @param {Highcharts.AnimationOptionsObject} [options]
  447. * Animation options.
  448. *
  449. * @param {Function} [complete]
  450. * Function to perform at the end of animation.
  451. *
  452. * @return {Highcharts.SVGElement}
  453. * Returns the SVGElement for chaining.
  454. */
  455. animate: function (params, options, complete) {
  456. var animOptions = H.animObject(
  457. pick(options, this.renderer.globalAnimation, true)
  458. );
  459. // When the page is hidden save resources in the background by not
  460. // running animation at all (#9749).
  461. if (pick(doc.hidden, doc.msHidden, doc.webkitHidden, false)) {
  462. animOptions.duration = 0;
  463. }
  464. if (animOptions.duration !== 0) {
  465. // allows using a callback with the global animation without
  466. // overwriting it
  467. if (complete) {
  468. animOptions.complete = complete;
  469. }
  470. animate(this, params, animOptions);
  471. } else {
  472. this.attr(params, null, complete);
  473. if (animOptions.step) {
  474. animOptions.step.call(this);
  475. }
  476. }
  477. return this;
  478. },
  479. /**
  480. * Build and apply an SVG gradient out of a common JavaScript configuration
  481. * object. This function is called from the attribute setters. An event
  482. * hook is added for supporting other complex color types.
  483. *
  484. * @private
  485. * @function Highcharts.SVGElement#complexColor
  486. *
  487. * @param {Highcharts.GradientColorObject} color
  488. * The gradient options structure.
  489. *
  490. * @param {string} prop
  491. * The property to apply, can either be `fill` or `stroke`.
  492. *
  493. * @param {Highcharts.SVGDOMElement} elem
  494. * SVG DOM element to apply the gradient on.
  495. */
  496. complexColor: function (color, prop, elem) {
  497. var renderer = this.renderer,
  498. colorObject,
  499. gradName,
  500. gradAttr,
  501. radAttr,
  502. gradients,
  503. gradientObject,
  504. stops,
  505. stopColor,
  506. stopOpacity,
  507. radialReference,
  508. id,
  509. key = [],
  510. value;
  511. H.fireEvent(this.renderer, 'complexColor', {
  512. args: arguments
  513. }, function () {
  514. // Apply linear or radial gradients
  515. if (color.radialGradient) {
  516. gradName = 'radialGradient';
  517. } else if (color.linearGradient) {
  518. gradName = 'linearGradient';
  519. }
  520. if (gradName) {
  521. gradAttr = color[gradName];
  522. gradients = renderer.gradients;
  523. stops = color.stops;
  524. radialReference = elem.radialReference;
  525. // Keep < 2.2 kompatibility
  526. if (isArray(gradAttr)) {
  527. color[gradName] = gradAttr = {
  528. x1: gradAttr[0],
  529. y1: gradAttr[1],
  530. x2: gradAttr[2],
  531. y2: gradAttr[3],
  532. gradientUnits: 'userSpaceOnUse'
  533. };
  534. }
  535. // Correct the radial gradient for the radial reference system
  536. if (
  537. gradName === 'radialGradient' &&
  538. radialReference &&
  539. !defined(gradAttr.gradientUnits)
  540. ) {
  541. // Save the radial attributes for updating
  542. radAttr = gradAttr;
  543. gradAttr = merge(
  544. gradAttr,
  545. renderer.getRadialAttr(radialReference, radAttr),
  546. { gradientUnits: 'userSpaceOnUse' }
  547. );
  548. }
  549. // Build the unique key to detect whether we need to create a
  550. // new element (#1282)
  551. objectEach(gradAttr, function (val, n) {
  552. if (n !== 'id') {
  553. key.push(n, val);
  554. }
  555. });
  556. objectEach(stops, function (val) {
  557. key.push(val);
  558. });
  559. key = key.join(',');
  560. // Check if a gradient object with the same config object is
  561. // created within this renderer
  562. if (gradients[key]) {
  563. id = gradients[key].attr('id');
  564. } else {
  565. // Set the id and create the element
  566. gradAttr.id = id = H.uniqueKey();
  567. gradients[key] = gradientObject =
  568. renderer.createElement(gradName)
  569. .attr(gradAttr)
  570. .add(renderer.defs);
  571. gradientObject.radAttr = radAttr;
  572. // The gradient needs to keep a list of stops to be able to
  573. // destroy them
  574. gradientObject.stops = [];
  575. stops.forEach(function (stop) {
  576. var stopObject;
  577. if (stop[1].indexOf('rgba') === 0) {
  578. colorObject = H.color(stop[1]);
  579. stopColor = colorObject.get('rgb');
  580. stopOpacity = colorObject.get('a');
  581. } else {
  582. stopColor = stop[1];
  583. stopOpacity = 1;
  584. }
  585. stopObject = renderer.createElement('stop').attr({
  586. offset: stop[0],
  587. 'stop-color': stopColor,
  588. 'stop-opacity': stopOpacity
  589. }).add(gradientObject);
  590. // Add the stop element to the gradient
  591. gradientObject.stops.push(stopObject);
  592. });
  593. }
  594. // Set the reference to the gradient object
  595. value = 'url(' + renderer.url + '#' + id + ')';
  596. elem.setAttribute(prop, value);
  597. elem.gradient = key;
  598. // Allow the color to be concatenated into tooltips formatters
  599. // etc. (#2995)
  600. color.toString = function () {
  601. return value;
  602. };
  603. }
  604. });
  605. },
  606. /**
  607. * Apply a text outline through a custom CSS property, by copying the text
  608. * element and apply stroke to the copy. Used internally. Contrast checks at
  609. * [example](https://jsfiddle.net/highcharts/43soe9m1/2/).
  610. *
  611. * @example
  612. * // Specific color
  613. * text.css({
  614. * textOutline: '1px black'
  615. * });
  616. * // Automatic contrast
  617. * text.css({
  618. * color: '#000000', // black text
  619. * textOutline: '1px contrast' // => white outline
  620. * });
  621. *
  622. * @private
  623. * @function Highcharts.SVGElement#applyTextOutline
  624. *
  625. * @param {string} textOutline
  626. * A custom CSS `text-outline` setting, defined by `width color`.
  627. */
  628. applyTextOutline: function (textOutline) {
  629. var elem = this.element,
  630. tspans,
  631. tspan,
  632. hasContrast = textOutline.indexOf('contrast') !== -1,
  633. styles = {},
  634. color,
  635. strokeWidth,
  636. firstRealChild,
  637. i;
  638. // When the text shadow is set to contrast, use dark stroke for light
  639. // text and vice versa.
  640. if (hasContrast) {
  641. styles.textOutline = textOutline = textOutline.replace(
  642. /contrast/g,
  643. this.renderer.getContrast(elem.style.fill)
  644. );
  645. }
  646. // Extract the stroke width and color
  647. textOutline = textOutline.split(' ');
  648. color = textOutline[textOutline.length - 1];
  649. strokeWidth = textOutline[0];
  650. if (strokeWidth && strokeWidth !== 'none' && H.svg) {
  651. this.fakeTS = true; // Fake text shadow
  652. tspans = [].slice.call(elem.getElementsByTagName('tspan'));
  653. // In order to get the right y position of the clone,
  654. // copy over the y setter
  655. this.ySetter = this.xSetter;
  656. // Since the stroke is applied on center of the actual outline, we
  657. // need to double it to get the correct stroke-width outside the
  658. // glyphs.
  659. strokeWidth = strokeWidth.replace(
  660. /(^[\d\.]+)(.*?)$/g,
  661. function (match, digit, unit) {
  662. return (2 * digit) + unit;
  663. }
  664. );
  665. // Remove shadows from previous runs. Iterate from the end to
  666. // support removing items inside the cycle (#6472).
  667. i = tspans.length;
  668. while (i--) {
  669. tspan = tspans[i];
  670. if (tspan.getAttribute('class') === 'highcharts-text-outline') {
  671. // Remove then erase
  672. erase(tspans, elem.removeChild(tspan));
  673. }
  674. }
  675. // For each of the tspans, create a stroked copy behind it.
  676. firstRealChild = elem.firstChild;
  677. tspans.forEach(function (tspan, y) {
  678. var clone;
  679. // Let the first line start at the correct X position
  680. if (y === 0) {
  681. tspan.setAttribute('x', elem.getAttribute('x'));
  682. y = elem.getAttribute('y');
  683. tspan.setAttribute('y', y || 0);
  684. if (y === null) {
  685. elem.setAttribute('y', 0);
  686. }
  687. }
  688. // Create the clone and apply outline properties
  689. clone = tspan.cloneNode(1);
  690. attr(clone, {
  691. 'class': 'highcharts-text-outline',
  692. 'fill': color,
  693. 'stroke': color,
  694. 'stroke-width': strokeWidth,
  695. 'stroke-linejoin': 'round'
  696. });
  697. elem.insertBefore(clone, firstRealChild);
  698. });
  699. }
  700. },
  701. // Custom attributes used for symbols, these should be filtered out when
  702. // setting SVGElement attributes (#9375).
  703. symbolCustomAttribs: [
  704. 'x',
  705. 'y',
  706. 'width',
  707. 'height',
  708. 'r',
  709. 'start',
  710. 'end',
  711. 'innerR',
  712. 'anchorX',
  713. 'anchorY',
  714. 'rounded'
  715. ],
  716. /**
  717. * Apply native and custom attributes to the SVG elements.
  718. *
  719. * In order to set the rotation center for rotation, set x and y to 0 and
  720. * use `translateX` and `translateY` attributes to position the element
  721. * instead.
  722. *
  723. * Attributes frequently used in Highcharts are `fill`, `stroke`,
  724. * `stroke-width`.
  725. *
  726. * @sample highcharts/members/renderer-rect/
  727. * Setting some attributes
  728. *
  729. * @example
  730. * // Set multiple attributes
  731. * element.attr({
  732. * stroke: 'red',
  733. * fill: 'blue',
  734. * x: 10,
  735. * y: 10
  736. * });
  737. *
  738. * // Set a single attribute
  739. * element.attr('stroke', 'red');
  740. *
  741. * // Get an attribute
  742. * element.attr('stroke'); // => 'red'
  743. *
  744. * @function Highcharts.SVGElement#attr
  745. *
  746. * @param {string|Highcharts.SVGAttributes} [hash]
  747. * The native and custom SVG attributes.
  748. *
  749. * @param {string} [val]
  750. * If the type of the first argument is `string`, the second can be a
  751. * value, which will serve as a single attribute setter. If the first
  752. * argument is a string and the second is undefined, the function
  753. * serves as a getter and the current value of the property is
  754. * returned.
  755. *
  756. * @param {Function} [complete]
  757. * A callback function to execute after setting the attributes. This
  758. * makes the function compliant and interchangeable with the
  759. * {@link SVGElement#animate} function.
  760. *
  761. * @param {boolean} [continueAnimation=true]
  762. * Used internally when `.attr` is called as part of an animation
  763. * step. Otherwise, calling `.attr` for an attribute will stop
  764. * animation for that attribute.
  765. *
  766. * @return {number|string|Highcharts.SVGElement}
  767. * If used as a setter, it returns the current
  768. * {@link Highcharts.SVGElement} so the calls can be chained. If
  769. * used as a getter, the current value of the attribute is returned.
  770. */
  771. attr: function (hash, val, complete, continueAnimation) {
  772. var key,
  773. element = this.element,
  774. hasSetSymbolSize,
  775. ret = this,
  776. skipAttr,
  777. setter,
  778. symbolCustomAttribs = this.symbolCustomAttribs;
  779. // single key-value pair
  780. if (typeof hash === 'string' && val !== undefined) {
  781. key = hash;
  782. hash = {};
  783. hash[key] = val;
  784. }
  785. // used as a getter: first argument is a string, second is undefined
  786. if (typeof hash === 'string') {
  787. ret = (this[hash + 'Getter'] || this._defaultGetter).call(
  788. this,
  789. hash,
  790. element
  791. );
  792. // setter
  793. } else {
  794. objectEach(hash, function eachAttribute(val, key) {
  795. skipAttr = false;
  796. // Unless .attr is from the animator update, stop current
  797. // running animation of this property
  798. if (!continueAnimation) {
  799. stop(this, key);
  800. }
  801. // Special handling of symbol attributes
  802. if (
  803. this.symbolName &&
  804. H.inArray(key, symbolCustomAttribs) !== -1
  805. ) {
  806. if (!hasSetSymbolSize) {
  807. this.symbolAttr(hash);
  808. hasSetSymbolSize = true;
  809. }
  810. skipAttr = true;
  811. }
  812. if (this.rotation && (key === 'x' || key === 'y')) {
  813. this.doTransform = true;
  814. }
  815. if (!skipAttr) {
  816. setter = this[key + 'Setter'] || this._defaultSetter;
  817. setter.call(this, val, key, element);
  818. // Let the shadow follow the main element
  819. if (
  820. !this.styledMode &&
  821. this.shadows &&
  822. /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/
  823. .test(key)
  824. ) {
  825. this.updateShadows(key, val, setter);
  826. }
  827. }
  828. }, this);
  829. this.afterSetters();
  830. }
  831. // In accordance with animate, run a complete callback
  832. if (complete) {
  833. complete.call(this);
  834. }
  835. return ret;
  836. },
  837. /**
  838. * This method is executed in the end of `attr()`, after setting all
  839. * attributes in the hash. In can be used to efficiently consolidate
  840. * multiple attributes in one SVG property -- e.g., translate, rotate and
  841. * scale are merged in one "transform" attribute in the SVG node.
  842. *
  843. * @private
  844. * @function Highcharts.SVGElement#afterSetters
  845. */
  846. afterSetters: function () {
  847. // Update transform. Do this outside the loop to prevent redundant
  848. // updating for batch setting of attributes.
  849. if (this.doTransform) {
  850. this.updateTransform();
  851. this.doTransform = false;
  852. }
  853. },
  854. /**
  855. * Update the shadow elements with new attributes.
  856. *
  857. * @private
  858. * @function Highcharts.SVGElement#updateShadows
  859. *
  860. * @param {string} key
  861. * The attribute name.
  862. *
  863. * @param {string|number} value
  864. * The value of the attribute.
  865. *
  866. * @param {Function} setter
  867. * The setter function, inherited from the parent wrapper.
  868. */
  869. updateShadows: function (key, value, setter) {
  870. var shadows = this.shadows,
  871. i = shadows.length;
  872. while (i--) {
  873. setter.call(
  874. shadows[i],
  875. key === 'height' ?
  876. Math.max(value - (shadows[i].cutHeight || 0), 0) :
  877. key === 'd' ? this.d : value,
  878. key,
  879. shadows[i]
  880. );
  881. }
  882. },
  883. /**
  884. * Add a class name to an element.
  885. *
  886. * @function Highcharts.SVGElement#addClass
  887. *
  888. * @param {string} className
  889. * The new class name to add.
  890. *
  891. * @param {boolean} [replace=false]
  892. * When true, the existing class name(s) will be overwritten with
  893. * the new one. When false, the new one is added.
  894. *
  895. * @return {Highcharts.SVGElement}
  896. * Return the SVG element for chainability.
  897. */
  898. addClass: function (className, replace) {
  899. var currentClassName = this.attr('class') || '';
  900. if (currentClassName.indexOf(className) === -1) {
  901. if (!replace) {
  902. className =
  903. (currentClassName + (currentClassName ? ' ' : '') +
  904. className).replace(' ', ' ');
  905. }
  906. this.attr('class', className);
  907. }
  908. return this;
  909. },
  910. /**
  911. * Check if an element has the given class name.
  912. *
  913. * @function Highcharts.SVGElement#hasClass
  914. *
  915. * @param {string} className
  916. * The class name to check for.
  917. *
  918. * @return {boolean}
  919. * Whether the class name is found.
  920. */
  921. hasClass: function (className) {
  922. return (this.attr('class') || '').split(' ').indexOf(className) !== -1;
  923. },
  924. /**
  925. * Remove a class name from the element.
  926. *
  927. * @function Highcharts.SVGElement#removeClass
  928. *
  929. * @param {string|RegExp} className
  930. * The class name to remove.
  931. *
  932. * @return {Highcharts.SVGElement} Returns the SVG element for chainability.
  933. */
  934. removeClass: function (className) {
  935. return this.attr(
  936. 'class',
  937. (this.attr('class') || '').replace(className, '')
  938. );
  939. },
  940. /**
  941. * If one of the symbol size affecting parameters are changed,
  942. * check all the others only once for each call to an element's
  943. * .attr() method
  944. *
  945. * @private
  946. * @function Highcharts.SVGElement#symbolAttr
  947. *
  948. * @param {Highcharts.Dictionary<number|string>} hash
  949. * The attributes to set.
  950. */
  951. symbolAttr: function (hash) {
  952. var wrapper = this;
  953. [
  954. 'x',
  955. 'y',
  956. 'r',
  957. 'start',
  958. 'end',
  959. 'width',
  960. 'height',
  961. 'innerR',
  962. 'anchorX',
  963. 'anchorY'
  964. ].forEach(function (key) {
  965. wrapper[key] = pick(hash[key], wrapper[key]);
  966. });
  967. wrapper.attr({
  968. d: wrapper.renderer.symbols[wrapper.symbolName](
  969. wrapper.x,
  970. wrapper.y,
  971. wrapper.width,
  972. wrapper.height,
  973. wrapper
  974. )
  975. });
  976. },
  977. /**
  978. * Apply a clipping rectangle to this element.
  979. *
  980. * @function Highcharts.SVGElement#clip
  981. *
  982. * @param {Highcharts.ClipRectElement} [clipRect]
  983. * The clipping rectangle. If skipped, the current clip is removed.
  984. *
  985. * @return {Highcharts.SVGElement}
  986. * Returns the SVG element to allow chaining.
  987. */
  988. clip: function (clipRect) {
  989. return this.attr(
  990. 'clip-path',
  991. clipRect ?
  992. 'url(' + this.renderer.url + '#' + clipRect.id + ')' :
  993. 'none'
  994. );
  995. },
  996. /**
  997. * Calculate the coordinates needed for drawing a rectangle crisply and
  998. * return the calculated attributes.
  999. *
  1000. * @function Highcharts.SVGElement#crisp
  1001. *
  1002. * @param {Highcharts.RectangleObject} rect
  1003. * Rectangle to crisp.
  1004. *
  1005. * @param {number} [strokeWidth]
  1006. * The stroke width to consider when computing crisp positioning. It
  1007. * can also be set directly on the rect parameter.
  1008. *
  1009. * @return {Highcharts.RectangleObject}
  1010. * The modified rectangle arguments.
  1011. */
  1012. crisp: function (rect, strokeWidth) {
  1013. var wrapper = this,
  1014. normalizer;
  1015. strokeWidth = strokeWidth || rect.strokeWidth || 0;
  1016. // Math.round because strokeWidth can sometimes have roundoff errors
  1017. normalizer = Math.round(strokeWidth) % 2 / 2;
  1018. // normalize for crisp edges
  1019. rect.x = Math.floor(rect.x || wrapper.x || 0) + normalizer;
  1020. rect.y = Math.floor(rect.y || wrapper.y || 0) + normalizer;
  1021. rect.width = Math.floor(
  1022. (rect.width || wrapper.width || 0) - 2 * normalizer
  1023. );
  1024. rect.height = Math.floor(
  1025. (rect.height || wrapper.height || 0) - 2 * normalizer
  1026. );
  1027. if (defined(rect.strokeWidth)) {
  1028. rect.strokeWidth = strokeWidth;
  1029. }
  1030. return rect;
  1031. },
  1032. /**
  1033. * Set styles for the element. In addition to CSS styles supported by
  1034. * native SVG and HTML elements, there are also some custom made for
  1035. * Highcharts, like `width`, `ellipsis` and `textOverflow` for SVG text
  1036. * elements.
  1037. *
  1038. * @sample highcharts/members/renderer-text-on-chart/
  1039. * Styled text
  1040. *
  1041. * @function Highcharts.SVGElement#css
  1042. *
  1043. * @param {Highcharts.CSSObject} styles
  1044. * The new CSS styles.
  1045. *
  1046. * @return {Highcharts.SVGElement}
  1047. * Return the SVG element for chaining.
  1048. */
  1049. css: function (styles) {
  1050. var oldStyles = this.styles,
  1051. newStyles = {},
  1052. elem = this.element,
  1053. textWidth,
  1054. serializedCss = '',
  1055. hyphenate,
  1056. hasNew = !oldStyles,
  1057. // These CSS properties are interpreted internally by the SVG
  1058. // renderer, but are not supported by SVG and should not be added to
  1059. // the DOM. In styled mode, no CSS should find its way to the DOM
  1060. // whatsoever (#6173, #6474).
  1061. svgPseudoProps = ['textOutline', 'textOverflow', 'width'];
  1062. // convert legacy
  1063. if (styles && styles.color) {
  1064. styles.fill = styles.color;
  1065. }
  1066. // Filter out existing styles to increase performance (#2640)
  1067. if (oldStyles) {
  1068. objectEach(styles, function (style, n) {
  1069. if (style !== oldStyles[n]) {
  1070. newStyles[n] = style;
  1071. hasNew = true;
  1072. }
  1073. });
  1074. }
  1075. if (hasNew) {
  1076. // Merge the new styles with the old ones
  1077. if (oldStyles) {
  1078. styles = extend(
  1079. oldStyles,
  1080. newStyles
  1081. );
  1082. }
  1083. // Get the text width from style
  1084. if (styles) {
  1085. // Previously set, unset it (#8234)
  1086. if (styles.width === null || styles.width === 'auto') {
  1087. delete this.textWidth;
  1088. // Apply new
  1089. } else if (
  1090. elem.nodeName.toLowerCase() === 'text' &&
  1091. styles.width
  1092. ) {
  1093. textWidth = this.textWidth = pInt(styles.width);
  1094. }
  1095. }
  1096. // store object
  1097. this.styles = styles;
  1098. if (textWidth && (!svg && this.renderer.forExport)) {
  1099. delete styles.width;
  1100. }
  1101. // Serialize and set style attribute
  1102. if (elem.namespaceURI === this.SVG_NS) { // #7633
  1103. hyphenate = function (a, b) {
  1104. return '-' + b.toLowerCase();
  1105. };
  1106. objectEach(styles, function (style, n) {
  1107. if (svgPseudoProps.indexOf(n) === -1) {
  1108. serializedCss +=
  1109. n.replace(/([A-Z])/g, hyphenate) + ':' +
  1110. style + ';';
  1111. }
  1112. });
  1113. if (serializedCss) {
  1114. attr(elem, 'style', serializedCss); // #1881
  1115. }
  1116. } else {
  1117. css(elem, styles);
  1118. }
  1119. if (this.added) {
  1120. // Rebuild text after added. Cache mechanisms in the buildText
  1121. // will prevent building if there are no significant changes.
  1122. if (this.element.nodeName === 'text') {
  1123. this.renderer.buildText(this);
  1124. }
  1125. // Apply text outline after added
  1126. if (styles && styles.textOutline) {
  1127. this.applyTextOutline(styles.textOutline);
  1128. }
  1129. }
  1130. }
  1131. return this;
  1132. },
  1133. /**
  1134. * Get the computed style. Only in styled mode.
  1135. *
  1136. * @example
  1137. * chart.series[0].points[0].graphic.getStyle('stroke-width'); // => '1px'
  1138. *
  1139. * @function Highcharts.SVGElement#getStyle
  1140. *
  1141. * @param {string} prop
  1142. * The property name to check for.
  1143. *
  1144. * @return {string}
  1145. * The current computed value.
  1146. */
  1147. getStyle: function (prop) {
  1148. return win.getComputedStyle(this.element || this, '')
  1149. .getPropertyValue(prop);
  1150. },
  1151. /**
  1152. * Get the computed stroke width in pixel values. This is used extensively
  1153. * when drawing shapes to ensure the shapes are rendered crisp and
  1154. * positioned correctly relative to each other. Using
  1155. * `shape-rendering: crispEdges` leaves us less control over positioning,
  1156. * for example when we want to stack columns next to each other, or position
  1157. * things pixel-perfectly within the plot box.
  1158. *
  1159. * The common pattern when placing a shape is:
  1160. * - Create the SVGElement and add it to the DOM. In styled mode, it will
  1161. * now receive a stroke width from the style sheet. In classic mode we
  1162. * will add the `stroke-width` attribute.
  1163. * - Read the computed `elem.strokeWidth()`.
  1164. * - Place it based on the stroke width.
  1165. *
  1166. * @function Highcharts.SVGElement#strokeWidth
  1167. *
  1168. * @return {number}
  1169. * The stroke width in pixels. Even if the given stroke widtch (in
  1170. * CSS or by attributes) is based on `em` or other units, the pixel
  1171. * size is returned.
  1172. */
  1173. strokeWidth: function () {
  1174. // In non-styled mode, read the stroke width as set by .attr
  1175. if (!this.renderer.styledMode) {
  1176. return this['stroke-width'] || 0;
  1177. }
  1178. // In styled mode, read computed stroke width
  1179. var val = this.getStyle('stroke-width'),
  1180. ret,
  1181. dummy;
  1182. // Read pixel values directly
  1183. if (val.indexOf('px') === val.length - 2) {
  1184. ret = pInt(val);
  1185. // Other values like em, pt etc need to be measured
  1186. } else {
  1187. dummy = doc.createElementNS(SVG_NS, 'rect');
  1188. attr(dummy, {
  1189. 'width': val,
  1190. 'stroke-width': 0
  1191. });
  1192. this.element.parentNode.appendChild(dummy);
  1193. ret = dummy.getBBox().width;
  1194. dummy.parentNode.removeChild(dummy);
  1195. }
  1196. return ret;
  1197. },
  1198. /**
  1199. * Add an event listener. This is a simple setter that replaces all other
  1200. * events of the same type, opposed to the {@link Highcharts#addEvent}
  1201. * function.
  1202. *
  1203. * @sample highcharts/members/element-on/
  1204. * A clickable rectangle
  1205. *
  1206. * @function Highcharts.SVGElement#on
  1207. *
  1208. * @param {string} eventType
  1209. * The event type. If the type is `click`, Highcharts will internally
  1210. * translate it to a `touchstart` event on touch devices, to prevent
  1211. * the browser from waiting for a click event from firing.
  1212. *
  1213. * @param {Function} handler
  1214. * The handler callback.
  1215. *
  1216. * @return {Highcharts.SVGElement}
  1217. * The SVGElement for chaining.
  1218. */
  1219. on: function (eventType, handler) {
  1220. var svgElement = this,
  1221. element = svgElement.element;
  1222. // touch
  1223. if (hasTouch && eventType === 'click') {
  1224. element.ontouchstart = function (e) {
  1225. svgElement.touchEventFired = Date.now(); // #2269
  1226. e.preventDefault();
  1227. handler.call(element, e);
  1228. };
  1229. element.onclick = function (e) {
  1230. if (win.navigator.userAgent.indexOf('Android') === -1 ||
  1231. Date.now() - (svgElement.touchEventFired || 0) > 1100) {
  1232. handler.call(element, e);
  1233. }
  1234. };
  1235. } else {
  1236. // simplest possible event model for internal use
  1237. element['on' + eventType] = handler;
  1238. }
  1239. return this;
  1240. },
  1241. /**
  1242. * Set the coordinates needed to draw a consistent radial gradient across
  1243. * a shape regardless of positioning inside the chart. Used on pie slices
  1244. * to make all the slices have the same radial reference point.
  1245. *
  1246. * @function Highcharts.SVGElement#setRadialReference
  1247. *
  1248. * @param {Array<number>} coordinates
  1249. * The center reference. The format is `[centerX, centerY, diameter]`
  1250. * in pixels.
  1251. *
  1252. * @return {Highcharts.SVGElement}
  1253. * Returns the SVGElement for chaining.
  1254. */
  1255. setRadialReference: function (coordinates) {
  1256. var existingGradient = this.renderer.gradients[this.element.gradient];
  1257. this.element.radialReference = coordinates;
  1258. // On redrawing objects with an existing gradient, the gradient needs
  1259. // to be repositioned (#3801)
  1260. if (existingGradient && existingGradient.radAttr) {
  1261. existingGradient.animate(
  1262. this.renderer.getRadialAttr(
  1263. coordinates,
  1264. existingGradient.radAttr
  1265. )
  1266. );
  1267. }
  1268. return this;
  1269. },
  1270. /**
  1271. * Move an object and its children by x and y values.
  1272. *
  1273. * @function Highcharts.SVGElement#translate
  1274. *
  1275. * @param {number} x
  1276. * The x value.
  1277. *
  1278. * @param {number} y
  1279. * The y value.
  1280. */
  1281. translate: function (x, y) {
  1282. return this.attr({
  1283. translateX: x,
  1284. translateY: y
  1285. });
  1286. },
  1287. /**
  1288. * Invert a group, rotate and flip. This is used internally on inverted
  1289. * charts, where the points and graphs are drawn as if not inverted, then
  1290. * the series group elements are inverted.
  1291. *
  1292. * @function Highcharts.SVGElement#invert
  1293. *
  1294. * @param {boolean} inverted
  1295. * Whether to invert or not. An inverted shape can be un-inverted by
  1296. * setting it to false.
  1297. *
  1298. * @return {Highcharts.SVGElement}
  1299. * Return the SVGElement for chaining.
  1300. */
  1301. invert: function (inverted) {
  1302. var wrapper = this;
  1303. wrapper.inverted = inverted;
  1304. wrapper.updateTransform();
  1305. return wrapper;
  1306. },
  1307. /**
  1308. * Update the transform attribute based on internal properties. Deals with
  1309. * the custom `translateX`, `translateY`, `rotation`, `scaleX` and `scaleY`
  1310. * attributes and updates the SVG `transform` attribute.
  1311. *
  1312. * @private
  1313. * @function Highcharts.SVGElement#updateTransform
  1314. */
  1315. updateTransform: function () {
  1316. var wrapper = this,
  1317. translateX = wrapper.translateX || 0,
  1318. translateY = wrapper.translateY || 0,
  1319. scaleX = wrapper.scaleX,
  1320. scaleY = wrapper.scaleY,
  1321. inverted = wrapper.inverted,
  1322. rotation = wrapper.rotation,
  1323. matrix = wrapper.matrix,
  1324. element = wrapper.element,
  1325. transform;
  1326. // Flipping affects translate as adjustment for flipping around the
  1327. // group's axis
  1328. if (inverted) {
  1329. translateX += wrapper.width;
  1330. translateY += wrapper.height;
  1331. }
  1332. // Apply translate. Nearly all transformed elements have translation,
  1333. // so instead of checking for translate = 0, do it always (#1767,
  1334. // #1846).
  1335. transform = ['translate(' + translateX + ',' + translateY + ')'];
  1336. // apply matrix
  1337. if (defined(matrix)) {
  1338. transform.push(
  1339. 'matrix(' + matrix.join(',') + ')'
  1340. );
  1341. }
  1342. // apply rotation
  1343. if (inverted) {
  1344. transform.push('rotate(90) scale(-1,1)');
  1345. } else if (rotation) { // text rotation
  1346. transform.push(
  1347. 'rotate(' + rotation + ' ' +
  1348. pick(this.rotationOriginX, element.getAttribute('x'), 0) +
  1349. ' ' +
  1350. pick(this.rotationOriginY, element.getAttribute('y') || 0) + ')'
  1351. );
  1352. }
  1353. // apply scale
  1354. if (defined(scaleX) || defined(scaleY)) {
  1355. transform.push(
  1356. 'scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'
  1357. );
  1358. }
  1359. if (transform.length) {
  1360. element.setAttribute('transform', transform.join(' '));
  1361. }
  1362. },
  1363. /**
  1364. * Bring the element to the front. Alternatively, a new zIndex can be set.
  1365. *
  1366. * @sample highcharts/members/element-tofront/
  1367. * Click an element to bring it to front
  1368. *
  1369. * @function Highcharts.SVGElement#toFront
  1370. *
  1371. * @return {Highcharts.SVGElement}
  1372. * Returns the SVGElement for chaining.
  1373. */
  1374. toFront: function () {
  1375. var element = this.element;
  1376. element.parentNode.appendChild(element);
  1377. return this;
  1378. },
  1379. /**
  1380. * Align the element relative to the chart or another box.
  1381. *
  1382. * @function Highcharts.SVGElement#align
  1383. *
  1384. * @param {Highcharts.AlignObject} [alignOptions]
  1385. * The alignment options. The function can be called without this
  1386. * parameter in order to re-align an element after the box has been
  1387. * updated.
  1388. *
  1389. * @param {boolean} [alignByTranslate]
  1390. * Align element by translation.
  1391. *
  1392. * @param {string|Highcharts.BBoxObject} [box]
  1393. * The box to align to, needs a width and height. When the box is a
  1394. * string, it refers to an object in the Renderer. For example, when
  1395. * box is `spacingBox`, it refers to `Renderer.spacingBox` which
  1396. * holds `width`, `height`, `x` and `y` properties.
  1397. *
  1398. * @return {Highcharts.SVGElement} Returns the SVGElement for chaining.
  1399. */
  1400. align: function (alignOptions, alignByTranslate, box) {
  1401. var align,
  1402. vAlign,
  1403. x,
  1404. y,
  1405. attribs = {},
  1406. alignTo,
  1407. renderer = this.renderer,
  1408. alignedObjects = renderer.alignedObjects,
  1409. alignFactor,
  1410. vAlignFactor;
  1411. // First call on instanciate
  1412. if (alignOptions) {
  1413. this.alignOptions = alignOptions;
  1414. this.alignByTranslate = alignByTranslate;
  1415. if (!box || isString(box)) {
  1416. this.alignTo = alignTo = box || 'renderer';
  1417. // prevent duplicates, like legendGroup after resize
  1418. erase(alignedObjects, this);
  1419. alignedObjects.push(this);
  1420. box = null; // reassign it below
  1421. }
  1422. // When called on resize, no arguments are supplied
  1423. } else {
  1424. alignOptions = this.alignOptions;
  1425. alignByTranslate = this.alignByTranslate;
  1426. alignTo = this.alignTo;
  1427. }
  1428. box = pick(box, renderer[alignTo], renderer);
  1429. // Assign variables
  1430. align = alignOptions.align;
  1431. vAlign = alignOptions.verticalAlign;
  1432. x = (box.x || 0) + (alignOptions.x || 0); // default: left align
  1433. y = (box.y || 0) + (alignOptions.y || 0); // default: top align
  1434. // Align
  1435. if (align === 'right') {
  1436. alignFactor = 1;
  1437. } else if (align === 'center') {
  1438. alignFactor = 2;
  1439. }
  1440. if (alignFactor) {
  1441. x += (box.width - (alignOptions.width || 0)) / alignFactor;
  1442. }
  1443. attribs[alignByTranslate ? 'translateX' : 'x'] = Math.round(x);
  1444. // Vertical align
  1445. if (vAlign === 'bottom') {
  1446. vAlignFactor = 1;
  1447. } else if (vAlign === 'middle') {
  1448. vAlignFactor = 2;
  1449. }
  1450. if (vAlignFactor) {
  1451. y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
  1452. }
  1453. attribs[alignByTranslate ? 'translateY' : 'y'] = Math.round(y);
  1454. // Animate only if already placed
  1455. this[this.placed ? 'animate' : 'attr'](attribs);
  1456. this.placed = true;
  1457. this.alignAttr = attribs;
  1458. return this;
  1459. },
  1460. /**
  1461. * Get the bounding box (width, height, x and y) for the element. Generally
  1462. * used to get rendered text size. Since this is called a lot in charts,
  1463. * the results are cached based on text properties, in order to save DOM
  1464. * traffic. The returned bounding box includes the rotation, so for example
  1465. * a single text line of rotation 90 will report a greater height, and a
  1466. * width corresponding to the line-height.
  1467. *
  1468. * @sample highcharts/members/renderer-on-chart/
  1469. * Draw a rectangle based on a text's bounding box
  1470. *
  1471. * @function Highcharts.SVGElement#getBBox
  1472. *
  1473. * @param {boolean} [reload]
  1474. * Skip the cache and get the updated DOM bouding box.
  1475. *
  1476. * @param {number} [rot]
  1477. * Override the element's rotation. This is internally used on axis
  1478. * labels with a value of 0 to find out what the bounding box would
  1479. * be have been if it were not rotated.
  1480. *
  1481. * @return {Highcharts.BBoxObject}
  1482. * The bounding box with `x`, `y`, `width` and `height` properties.
  1483. */
  1484. getBBox: function (reload, rot) {
  1485. var wrapper = this,
  1486. bBox, // = wrapper.bBox,
  1487. renderer = wrapper.renderer,
  1488. width,
  1489. height,
  1490. rotation,
  1491. rad,
  1492. element = wrapper.element,
  1493. styles = wrapper.styles,
  1494. fontSize,
  1495. textStr = wrapper.textStr,
  1496. toggleTextShadowShim,
  1497. cache = renderer.cache,
  1498. cacheKeys = renderer.cacheKeys,
  1499. isSVG = element.namespaceURI === wrapper.SVG_NS,
  1500. cacheKey;
  1501. rotation = pick(rot, wrapper.rotation);
  1502. rad = rotation * deg2rad;
  1503. fontSize = renderer.styledMode ? (
  1504. element &&
  1505. SVGElement.prototype.getStyle.call(element, 'font-size')
  1506. ) : (
  1507. styles && styles.fontSize
  1508. );
  1509. // Avoid undefined and null (#7316)
  1510. if (defined(textStr)) {
  1511. cacheKey = textStr.toString();
  1512. // Since numbers are monospaced, and numerical labels appear a lot
  1513. // in a chart, we assume that a label of n characters has the same
  1514. // bounding box as others of the same length. Unless there is inner
  1515. // HTML in the label. In that case, leave the numbers as is (#5899).
  1516. if (cacheKey.indexOf('<') === -1) {
  1517. cacheKey = cacheKey.replace(/[0-9]/g, '0');
  1518. }
  1519. // Properties that affect bounding box
  1520. cacheKey += [
  1521. '',
  1522. rotation || 0,
  1523. fontSize,
  1524. wrapper.textWidth, // #7874, also useHTML
  1525. styles && styles.textOverflow // #5968
  1526. ].join(',');
  1527. }
  1528. if (cacheKey && !reload) {
  1529. bBox = cache[cacheKey];
  1530. }
  1531. // No cache found
  1532. if (!bBox) {
  1533. // SVG elements
  1534. if (isSVG || renderer.forExport) {
  1535. try { // Fails in Firefox if the container has display: none.
  1536. // When the text shadow shim is used, we need to hide the
  1537. // fake shadows to get the correct bounding box (#3872)
  1538. toggleTextShadowShim = this.fakeTS && function (display) {
  1539. [].forEach.call(
  1540. element.querySelectorAll(
  1541. '.highcharts-text-outline'
  1542. ),
  1543. function (tspan) {
  1544. tspan.style.display = display;
  1545. }
  1546. );
  1547. };
  1548. // Workaround for #3842, Firefox reporting wrong bounding
  1549. // box for shadows
  1550. if (toggleTextShadowShim) {
  1551. toggleTextShadowShim('none');
  1552. }
  1553. bBox = element.getBBox ?
  1554. // SVG: use extend because IE9 is not allowed to change
  1555. // width and height in case of rotation (below)
  1556. extend({}, element.getBBox()) : {
  1557. // Legacy IE in export mode
  1558. width: element.offsetWidth,
  1559. height: element.offsetHeight
  1560. };
  1561. // #3842
  1562. if (toggleTextShadowShim) {
  1563. toggleTextShadowShim('');
  1564. }
  1565. } catch (e) {}
  1566. // If the bBox is not set, the try-catch block above failed. The
  1567. // other condition is for Opera that returns a width of
  1568. // -Infinity on hidden elements.
  1569. if (!bBox || bBox.width < 0) {
  1570. bBox = { width: 0, height: 0 };
  1571. }
  1572. // VML Renderer or useHTML within SVG
  1573. } else {
  1574. bBox = wrapper.htmlGetBBox();
  1575. }
  1576. // True SVG elements as well as HTML elements in modern browsers
  1577. // using the .useHTML option need to compensated for rotation
  1578. if (renderer.isSVG) {
  1579. width = bBox.width;
  1580. height = bBox.height;
  1581. // Workaround for wrong bounding box in IE, Edge and Chrome on
  1582. // Windows. With Highcharts' default font, IE and Edge report
  1583. // a box height of 16.899 and Chrome rounds it to 17. If this
  1584. // stands uncorrected, it results in more padding added below
  1585. // the text than above when adding a label border or background.
  1586. // Also vertical positioning is affected.
  1587. // https://jsfiddle.net/highcharts/em37nvuj/
  1588. // (#1101, #1505, #1669, #2568, #6213).
  1589. if (isSVG) {
  1590. bBox.height = height = (
  1591. {
  1592. '11px,17': 14,
  1593. '13px,20': 16
  1594. }[
  1595. styles && styles.fontSize + ',' + Math.round(height)
  1596. ] ||
  1597. height
  1598. );
  1599. }
  1600. // Adjust for rotated text
  1601. if (rotation) {
  1602. bBox.width = Math.abs(height * Math.sin(rad)) +
  1603. Math.abs(width * Math.cos(rad));
  1604. bBox.height = Math.abs(height * Math.cos(rad)) +
  1605. Math.abs(width * Math.sin(rad));
  1606. }
  1607. }
  1608. // Cache it. When loading a chart in a hidden iframe in Firefox and
  1609. // IE/Edge, the bounding box height is 0, so don't cache it (#5620).
  1610. if (cacheKey && bBox.height > 0) {
  1611. // Rotate (#4681)
  1612. while (cacheKeys.length > 250) {
  1613. delete cache[cacheKeys.shift()];
  1614. }
  1615. if (!cache[cacheKey]) {
  1616. cacheKeys.push(cacheKey);
  1617. }
  1618. cache[cacheKey] = bBox;
  1619. }
  1620. }
  1621. return bBox;
  1622. },
  1623. /**
  1624. * Show the element after it has been hidden.
  1625. *
  1626. * @function Highcharts.SVGElement#show
  1627. *
  1628. * @param {boolean} [inherit=false]
  1629. * Set the visibility attribute to `inherit` rather than `visible`.
  1630. * The difference is that an element with `visibility="visible"`
  1631. * will be visible even if the parent is hidden.
  1632. *
  1633. * @return {Highcharts.SVGElement}
  1634. * Returns the SVGElement for chaining.
  1635. */
  1636. show: function (inherit) {
  1637. return this.attr({ visibility: inherit ? 'inherit' : 'visible' });
  1638. },
  1639. /**
  1640. * Hide the element, equivalent to setting the `visibility` attribute to
  1641. * `hidden`.
  1642. *
  1643. * @function Highcharts.SVGElement#hide
  1644. *
  1645. * @return {Highcharts.SVGElement}
  1646. * Returns the SVGElement for chaining.
  1647. */
  1648. hide: function () {
  1649. return this.attr({ visibility: 'hidden' });
  1650. },
  1651. /**
  1652. * Fade out an element by animating its opacity down to 0, and hide it on
  1653. * complete. Used internally for the tooltip.
  1654. *
  1655. * @function Highcharts.SVGElement#fadeOut
  1656. *
  1657. * @param {number} [duration=150]
  1658. * The fade duration in milliseconds.
  1659. */
  1660. fadeOut: function (duration) {
  1661. var elemWrapper = this;
  1662. elemWrapper.animate({
  1663. opacity: 0
  1664. }, {
  1665. duration: duration || 150,
  1666. complete: function () {
  1667. // #3088, assuming we're only using this for tooltips
  1668. elemWrapper.attr({ y: -9999 });
  1669. }
  1670. });
  1671. },
  1672. /**
  1673. * Add the element to the DOM. All elements must be added this way.
  1674. *
  1675. * @sample highcharts/members/renderer-g
  1676. * Elements added to a group
  1677. *
  1678. * @function Highcharts.SVGElement#add
  1679. *
  1680. * @param {Highcharts.SVGElement|Highcharts.SVGDOMElement} [parent]
  1681. * The parent item to add it to. If undefined, the element is added
  1682. * to the {@link Highcharts.SVGRenderer.box}.
  1683. *
  1684. * @return {Highcharts.SVGElement}
  1685. * Returns the SVGElement for chaining.
  1686. */
  1687. add: function (parent) {
  1688. var renderer = this.renderer,
  1689. element = this.element,
  1690. inserted;
  1691. if (parent) {
  1692. this.parentGroup = parent;
  1693. }
  1694. // mark as inverted
  1695. this.parentInverted = parent && parent.inverted;
  1696. // build formatted text
  1697. if (this.textStr !== undefined) {
  1698. renderer.buildText(this);
  1699. }
  1700. // Mark as added
  1701. this.added = true;
  1702. // If we're adding to renderer root, or other elements in the group
  1703. // have a z index, we need to handle it
  1704. if (!parent || parent.handleZ || this.zIndex) {
  1705. inserted = this.zIndexSetter();
  1706. }
  1707. // If zIndex is not handled, append at the end
  1708. if (!inserted) {
  1709. (parent ? parent.element : renderer.box).appendChild(element);
  1710. }
  1711. // fire an event for internal hooks
  1712. if (this.onAdd) {
  1713. this.onAdd();
  1714. }
  1715. return this;
  1716. },
  1717. /**
  1718. * Removes an element from the DOM.
  1719. *
  1720. * @private
  1721. * @function Highcharts.SVGElement#safeRemoveChild
  1722. *
  1723. * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element
  1724. * The DOM node to remove.
  1725. */
  1726. safeRemoveChild: function (element) {
  1727. var parentNode = element.parentNode;
  1728. if (parentNode) {
  1729. parentNode.removeChild(element);
  1730. }
  1731. },
  1732. /**
  1733. * Destroy the element and element wrapper and clear up the DOM and event
  1734. * hooks.
  1735. *
  1736. * @function Highcharts.SVGElement#destroy
  1737. */
  1738. destroy: function () {
  1739. var wrapper = this,
  1740. element = wrapper.element || {},
  1741. renderer = wrapper.renderer,
  1742. parentToClean =
  1743. renderer.isSVG &&
  1744. element.nodeName === 'SPAN' &&
  1745. wrapper.parentGroup,
  1746. grandParent,
  1747. ownerSVGElement = element.ownerSVGElement,
  1748. i,
  1749. clipPath = wrapper.clipPath;
  1750. // remove events
  1751. element.onclick = element.onmouseout = element.onmouseover =
  1752. element.onmousemove = element.point = null;
  1753. stop(wrapper); // stop running animations
  1754. if (clipPath && ownerSVGElement) {
  1755. // Look for existing references to this clipPath and remove them
  1756. // before destroying the element (#6196).
  1757. // The upper case version is for Edge
  1758. [].forEach.call(
  1759. ownerSVGElement.querySelectorAll('[clip-path],[CLIP-PATH]'),
  1760. function (el) {
  1761. var clipPathAttr = el.getAttribute('clip-path'),
  1762. clipPathId = clipPath.element.id;
  1763. // Include the closing paranthesis in the test to rule out
  1764. // id's from 10 and above (#6550). Edge puts quotes inside
  1765. // the url, others not.
  1766. if (
  1767. clipPathAttr.indexOf('(#' + clipPathId + ')') > -1 ||
  1768. clipPathAttr.indexOf('("#' + clipPathId + '")') > -1
  1769. ) {
  1770. el.removeAttribute('clip-path');
  1771. }
  1772. }
  1773. );
  1774. wrapper.clipPath = clipPath.destroy();
  1775. }
  1776. // Destroy stops in case this is a gradient object
  1777. if (wrapper.stops) {
  1778. for (i = 0; i < wrapper.stops.length; i++) {
  1779. wrapper.stops[i] = wrapper.stops[i].destroy();
  1780. }
  1781. wrapper.stops = null;
  1782. }
  1783. // remove element
  1784. wrapper.safeRemoveChild(element);
  1785. if (!renderer.styledMode) {
  1786. wrapper.destroyShadows();
  1787. }
  1788. // In case of useHTML, clean up empty containers emulating SVG groups
  1789. // (#1960, #2393, #2697).
  1790. while (
  1791. parentToClean &&
  1792. parentToClean.div &&
  1793. parentToClean.div.childNodes.length === 0
  1794. ) {
  1795. grandParent = parentToClean.parentGroup;
  1796. wrapper.safeRemoveChild(parentToClean.div);
  1797. delete parentToClean.div;
  1798. parentToClean = grandParent;
  1799. }
  1800. // remove from alignObjects
  1801. if (wrapper.alignTo) {
  1802. erase(renderer.alignedObjects, wrapper);
  1803. }
  1804. objectEach(wrapper, function (val, key) {
  1805. delete wrapper[key];
  1806. });
  1807. return null;
  1808. },
  1809. /**
  1810. * Add a shadow to the element. Must be called after the element is added to
  1811. * the DOM. In styled mode, this method is not used, instead use `defs` and
  1812. * filters.
  1813. *
  1814. * @example
  1815. * renderer.rect(10, 100, 100, 100)
  1816. * .attr({ fill: 'red' })
  1817. * .shadow(true);
  1818. *
  1819. * @function Highcharts.SVGElement#shadow
  1820. *
  1821. * @param {boolean|Highcharts.ShadowOptionsObject} shadowOptions
  1822. * The shadow options. If `true`, the default options are applied. If
  1823. * `false`, the current shadow will be removed.
  1824. *
  1825. * @param {Highcharts.SVGElement} [group]
  1826. * The SVG group element where the shadows will be applied. The
  1827. * default is to add it to the same parent as the current element.
  1828. * Internally, this is ised for pie slices, where all the shadows are
  1829. * added to an element behind all the slices.
  1830. *
  1831. * @param {boolean} [cutOff]
  1832. * Used internally for column shadows.
  1833. *
  1834. * @return {Highcharts.SVGElement}
  1835. * Returns the SVGElement for chaining.
  1836. */
  1837. shadow: function (shadowOptions, group, cutOff) {
  1838. var shadows = [],
  1839. i,
  1840. shadow,
  1841. element = this.element,
  1842. strokeWidth,
  1843. shadowWidth,
  1844. shadowElementOpacity,
  1845. // compensate for inverted plot area
  1846. transform;
  1847. if (!shadowOptions) {
  1848. this.destroyShadows();
  1849. } else if (!this.shadows) {
  1850. shadowWidth = pick(shadowOptions.width, 3);
  1851. shadowElementOpacity = (shadowOptions.opacity || 0.15) /
  1852. shadowWidth;
  1853. transform = this.parentInverted ?
  1854. '(-1,-1)' :
  1855. '(' + pick(shadowOptions.offsetX, 1) + ', ' +
  1856. pick(shadowOptions.offsetY, 1) + ')';
  1857. for (i = 1; i <= shadowWidth; i++) {
  1858. shadow = element.cloneNode(0);
  1859. strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
  1860. attr(shadow, {
  1861. 'stroke':
  1862. shadowOptions.color || '#000000',
  1863. 'stroke-opacity': shadowElementOpacity * i,
  1864. 'stroke-width': strokeWidth,
  1865. 'transform': 'translate' + transform,
  1866. 'fill': 'none'
  1867. });
  1868. shadow.setAttribute(
  1869. 'class',
  1870. (shadow.getAttribute('class') || '') + ' highcharts-shadow'
  1871. );
  1872. if (cutOff) {
  1873. attr(
  1874. shadow,
  1875. 'height',
  1876. Math.max(attr(shadow, 'height') - strokeWidth, 0)
  1877. );
  1878. shadow.cutHeight = strokeWidth;
  1879. }
  1880. if (group) {
  1881. group.element.appendChild(shadow);
  1882. } else if (element.parentNode) {
  1883. element.parentNode.insertBefore(shadow, element);
  1884. }
  1885. shadows.push(shadow);
  1886. }
  1887. this.shadows = shadows;
  1888. }
  1889. return this;
  1890. },
  1891. /**
  1892. * Destroy shadows on the element.
  1893. *
  1894. * @private
  1895. * @function Highcharts.SVGElement#destroyShadows
  1896. */
  1897. destroyShadows: function () {
  1898. (this.shadows || []).forEach(function (shadow) {
  1899. this.safeRemoveChild(shadow);
  1900. }, this);
  1901. this.shadows = undefined;
  1902. },
  1903. /**
  1904. * @private
  1905. * @function Highcharts.SVGElement#xGetter
  1906. *
  1907. * @param {string} key
  1908. *
  1909. * @return {number|string|null}
  1910. */
  1911. xGetter: function (key) {
  1912. if (this.element.nodeName === 'circle') {
  1913. if (key === 'x') {
  1914. key = 'cx';
  1915. } else if (key === 'y') {
  1916. key = 'cy';
  1917. }
  1918. }
  1919. return this._defaultGetter(key);
  1920. },
  1921. /**
  1922. * Get the current value of an attribute or pseudo attribute,
  1923. * used mainly for animation. Called internally from
  1924. * the {@link Highcharts.SVGRenderer#attr} function.
  1925. *
  1926. * @private
  1927. * @function Highcharts.SVGElement#_defaultGetter
  1928. *
  1929. * @param {string} key
  1930. * Property key.
  1931. *
  1932. * @return {number|string|null}
  1933. * Property value.
  1934. */
  1935. _defaultGetter: function (key) {
  1936. var ret = pick(
  1937. this[key + 'Value'], // align getter
  1938. this[key],
  1939. this.element ? this.element.getAttribute(key) : null,
  1940. 0
  1941. );
  1942. if (/^[\-0-9\.]+$/.test(ret)) { // is numerical
  1943. ret = parseFloat(ret);
  1944. }
  1945. return ret;
  1946. },
  1947. /**
  1948. * @private
  1949. * @function Highcharts.SVGElement#dSettter
  1950. *
  1951. * @param {number|string|Highcharts.SVGPathArray} value
  1952. *
  1953. * @param {string} key
  1954. *
  1955. * @param {Highcharts.SVGDOMElement} element
  1956. */
  1957. dSetter: function (value, key, element) {
  1958. if (value && value.join) { // join path
  1959. value = value.join(' ');
  1960. }
  1961. if (/(NaN| {2}|^$)/.test(value)) {
  1962. value = 'M 0 0';
  1963. }
  1964. // Check for cache before resetting. Resetting causes disturbance in the
  1965. // DOM, causing flickering in some cases in Edge/IE (#6747). Also
  1966. // possible performance gain.
  1967. if (this[key] !== value) {
  1968. element.setAttribute(key, value);
  1969. this[key] = value;
  1970. }
  1971. },
  1972. /**
  1973. * @private
  1974. * @function Highcharts.SVGElement#dashstyleSetter
  1975. *
  1976. * @param {string} value
  1977. */
  1978. dashstyleSetter: function (value) {
  1979. var i,
  1980. strokeWidth = this['stroke-width'];
  1981. // If "inherit", like maps in IE, assume 1 (#4981). With HC5 and the new
  1982. // strokeWidth function, we should be able to use that instead.
  1983. if (strokeWidth === 'inherit') {
  1984. strokeWidth = 1;
  1985. }
  1986. value = value && value.toLowerCase();
  1987. if (value) {
  1988. value = value
  1989. .replace('shortdashdotdot', '3,1,1,1,1,1,')
  1990. .replace('shortdashdot', '3,1,1,1')
  1991. .replace('shortdot', '1,1,')
  1992. .replace('shortdash', '3,1,')
  1993. .replace('longdash', '8,3,')
  1994. .replace(/dot/g, '1,3,')
  1995. .replace('dash', '4,3,')
  1996. .replace(/,$/, '')
  1997. .split(','); // ending comma
  1998. i = value.length;
  1999. while (i--) {
  2000. value[i] = pInt(value[i]) * strokeWidth;
  2001. }
  2002. value = value.join(',')
  2003. .replace(/NaN/g, 'none'); // #3226
  2004. this.element.setAttribute('stroke-dasharray', value);
  2005. }
  2006. },
  2007. /**
  2008. * @private
  2009. * @function Highcharts.SVGElement#alignSetter
  2010. *
  2011. * @param {"start"|"middle"|"end"} value
  2012. */
  2013. alignSetter: function (value) {
  2014. var convert = { left: 'start', center: 'middle', right: 'end' };
  2015. this.alignValue = value;
  2016. this.element.setAttribute('text-anchor', convert[value]);
  2017. },
  2018. /**
  2019. * @private
  2020. * @function Highcharts.SVGElement#opacitySetter
  2021. *
  2022. * @param {string} value
  2023. *
  2024. * @param {string} key
  2025. *
  2026. * @param {Highcharts.SVGDOMElement} element
  2027. */
  2028. opacitySetter: function (value, key, element) {
  2029. this[key] = value;
  2030. element.setAttribute(key, value);
  2031. },
  2032. /**
  2033. * @private
  2034. * @function Highcharts.SVGElement#titleSetter
  2035. *
  2036. * @param {string} value
  2037. */
  2038. titleSetter: function (value) {
  2039. var titleNode = this.element.getElementsByTagName('title')[0];
  2040. if (!titleNode) {
  2041. titleNode = doc.createElementNS(this.SVG_NS, 'title');
  2042. this.element.appendChild(titleNode);
  2043. }
  2044. // Remove text content if it exists
  2045. if (titleNode.firstChild) {
  2046. titleNode.removeChild(titleNode.firstChild);
  2047. }
  2048. titleNode.appendChild(
  2049. doc.createTextNode(
  2050. // #3276, #3895
  2051. (String(pick(value), ''))
  2052. .replace(/<[^>]*>/g, '')
  2053. .replace(/&lt;/g, '<')
  2054. .replace(/&gt;/g, '>')
  2055. )
  2056. );
  2057. },
  2058. /**
  2059. * @private
  2060. * @function Highcharts.SVGElement#textSetter
  2061. *
  2062. * @param {string} value
  2063. */
  2064. textSetter: function (value) {
  2065. if (value !== this.textStr) {
  2066. // Delete bBox memo when the text changes
  2067. delete this.bBox;
  2068. this.textStr = value;
  2069. if (this.added) {
  2070. this.renderer.buildText(this);
  2071. }
  2072. }
  2073. },
  2074. /**
  2075. * @private
  2076. * @function Highcharts.SVGElement#fillSetter
  2077. *
  2078. * @param {Highcharts.Color|Highcharts.ColorString} value
  2079. *
  2080. * @param {string} key
  2081. *
  2082. * @param {Highcharts.SVGDOMElement} element
  2083. */
  2084. fillSetter: function (value, key, element) {
  2085. if (typeof value === 'string') {
  2086. element.setAttribute(key, value);
  2087. } else if (value) {
  2088. this.complexColor(value, key, element);
  2089. }
  2090. },
  2091. /**
  2092. * @private
  2093. * @function Highcharts.SVGElement#visibilitySetter
  2094. *
  2095. * @param {string} value
  2096. *
  2097. * @param {string} key
  2098. *
  2099. * @param {Highcharts.SVGDOMElement} element
  2100. */
  2101. visibilitySetter: function (value, key, element) {
  2102. // IE9-11 doesn't handle visibilty:inherit well, so we remove the
  2103. // attribute instead (#2881, #3909)
  2104. if (value === 'inherit') {
  2105. element.removeAttribute(key);
  2106. } else if (this[key] !== value) { // #6747
  2107. element.setAttribute(key, value);
  2108. }
  2109. this[key] = value;
  2110. },
  2111. /**
  2112. * @private
  2113. * @function Highcharts.SVGElement#zIndexSetter
  2114. *
  2115. * @param {string} value
  2116. *
  2117. * @param {string} key
  2118. *
  2119. * @return {boolean}
  2120. */
  2121. zIndexSetter: function (value, key) {
  2122. var renderer = this.renderer,
  2123. parentGroup = this.parentGroup,
  2124. parentWrapper = parentGroup || renderer,
  2125. parentNode = parentWrapper.element || renderer.box,
  2126. childNodes,
  2127. otherElement,
  2128. otherZIndex,
  2129. element = this.element,
  2130. inserted,
  2131. undefinedOtherZIndex,
  2132. svgParent = parentNode === renderer.box,
  2133. run = this.added,
  2134. i;
  2135. if (defined(value)) {
  2136. // So we can read it for other elements in the group
  2137. element.setAttribute('data-z-index', value);
  2138. value = +value;
  2139. if (this[key] === value) { // Only update when needed (#3865)
  2140. run = false;
  2141. }
  2142. } else if (defined(this[key])) {
  2143. element.removeAttribute('data-z-index');
  2144. }
  2145. this[key] = value;
  2146. // Insert according to this and other elements' zIndex. Before .add() is
  2147. // called, nothing is done. Then on add, or by later calls to
  2148. // zIndexSetter, the node is placed on the right place in the DOM.
  2149. if (run) {
  2150. value = this.zIndex;
  2151. if (value && parentGroup) {
  2152. parentGroup.handleZ = true;
  2153. }
  2154. childNodes = parentNode.childNodes;
  2155. for (i = childNodes.length - 1; i >= 0 && !inserted; i--) {
  2156. otherElement = childNodes[i];
  2157. otherZIndex = otherElement.getAttribute('data-z-index');
  2158. undefinedOtherZIndex = !defined(otherZIndex);
  2159. if (otherElement !== element) {
  2160. if (
  2161. // Negative zIndex versus no zIndex:
  2162. // On all levels except the highest. If the parent is
  2163. // <svg>, then we don't want to put items before <desc>
  2164. // or <defs>
  2165. (value < 0 && undefinedOtherZIndex && !svgParent && !i)
  2166. ) {
  2167. parentNode.insertBefore(element, childNodes[i]);
  2168. inserted = true;
  2169. } else if (
  2170. // Insert after the first element with a lower zIndex
  2171. pInt(otherZIndex) <= value ||
  2172. // If negative zIndex, add this before first undefined
  2173. // zIndex element
  2174. (
  2175. undefinedOtherZIndex &&
  2176. (!defined(value) || value >= 0)
  2177. )
  2178. ) {
  2179. parentNode.insertBefore(
  2180. element,
  2181. childNodes[i + 1] || null // null for oldIE export
  2182. );
  2183. inserted = true;
  2184. }
  2185. }
  2186. }
  2187. if (!inserted) {
  2188. parentNode.insertBefore(
  2189. element,
  2190. childNodes[svgParent ? 3 : 0] || null // null for oldIE
  2191. );
  2192. inserted = true;
  2193. }
  2194. }
  2195. return inserted;
  2196. },
  2197. /**
  2198. * @private
  2199. * @function Highcharts.SVGElement#_defaultSetter
  2200. *
  2201. * @param {string} value
  2202. *
  2203. * @param {string} key
  2204. *
  2205. * @param {Highcharts.SVGDOMElement} element
  2206. */
  2207. _defaultSetter: function (value, key, element) {
  2208. element.setAttribute(key, value);
  2209. }
  2210. });
  2211. // Some shared setters and getters
  2212. SVGElement.prototype.yGetter =
  2213. SVGElement.prototype.xGetter;
  2214. SVGElement.prototype.translateXSetter =
  2215. SVGElement.prototype.translateYSetter =
  2216. SVGElement.prototype.rotationSetter =
  2217. SVGElement.prototype.verticalAlignSetter =
  2218. SVGElement.prototype.rotationOriginXSetter =
  2219. SVGElement.prototype.rotationOriginYSetter =
  2220. SVGElement.prototype.scaleXSetter =
  2221. SVGElement.prototype.scaleYSetter =
  2222. SVGElement.prototype.matrixSetter = function (value, key) {
  2223. this[key] = value;
  2224. this.doTransform = true;
  2225. };
  2226. // WebKit and Batik have problems with a stroke-width of zero, so in this case
  2227. // we remove the stroke attribute altogether. #1270, #1369, #3065, #3072.
  2228. SVGElement.prototype['stroke-widthSetter'] =
  2229. /**
  2230. * @private
  2231. * @function Highcharts.SVGElement#strokeSetter
  2232. *
  2233. * @param {number|string} value
  2234. *
  2235. * @param {string} key
  2236. *
  2237. * @param {Highcharts.SVGDOMElement} element
  2238. */
  2239. SVGElement.prototype.strokeSetter = function (value, key, element) {
  2240. this[key] = value;
  2241. // Only apply the stroke attribute if the stroke width is defined and larger
  2242. // than 0
  2243. if (this.stroke && this['stroke-width']) {
  2244. // Use prototype as instance may be overridden
  2245. SVGElement.prototype.fillSetter.call(
  2246. this,
  2247. this.stroke,
  2248. 'stroke',
  2249. element
  2250. );
  2251. element.setAttribute('stroke-width', this['stroke-width']);
  2252. this.hasStroke = true;
  2253. } else if (key === 'stroke-width' && value === 0 && this.hasStroke) {
  2254. element.removeAttribute('stroke');
  2255. this.hasStroke = false;
  2256. }
  2257. };
  2258. /**
  2259. * Allows direct access to the Highcharts rendering layer in order to draw
  2260. * primitive shapes like circles, rectangles, paths or text directly on a chart,
  2261. * or independent from any chart. The SVGRenderer represents a wrapper object
  2262. * for SVG in modern browsers. Through the VMLRenderer, part of the `oldie.js`
  2263. * module, it also brings vector graphics to IE <= 8.
  2264. *
  2265. * An existing chart's renderer can be accessed through {@link Chart.renderer}.
  2266. * The renderer can also be used completely decoupled from a chart.
  2267. *
  2268. * @sample highcharts/members/renderer-on-chart
  2269. * Annotating a chart programmatically.
  2270. * @sample highcharts/members/renderer-basic
  2271. * Independent SVG drawing.
  2272. *
  2273. * @example
  2274. * // Use directly without a chart object.
  2275. * var renderer = new Highcharts.Renderer(parentNode, 600, 400);
  2276. *
  2277. * @class
  2278. * @name Highcharts.SVGRenderer
  2279. *
  2280. * @param {Highcharts.HTMLDOMElement} container
  2281. * Where to put the SVG in the web page.
  2282. *
  2283. * @param {number} width
  2284. * The width of the SVG.
  2285. *
  2286. * @param {number} height
  2287. * The height of the SVG.
  2288. *
  2289. * @param {boolean} [forExport=false]
  2290. * Whether the rendered content is intended for export.
  2291. *
  2292. * @param {boolean} [allowHTML=true]
  2293. * Whether the renderer is allowed to include HTML text, which will be
  2294. * projected on top of the SVG.
  2295. */
  2296. SVGRenderer = H.SVGRenderer = function () {
  2297. this.init.apply(this, arguments);
  2298. };
  2299. extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
  2300. /**
  2301. * A pointer to the renderer's associated Element class. The VMLRenderer
  2302. * will have a pointer to VMLElement here.
  2303. *
  2304. * @name Highcharts.SVGRenderer#Element
  2305. * @type {Highcharts.SVGElement}
  2306. */
  2307. Element: SVGElement,
  2308. SVG_NS: SVG_NS,
  2309. /**
  2310. * Initialize the SVGRenderer. Overridable initiator function that takes
  2311. * the same parameters as the constructor.
  2312. *
  2313. * @function Highcharts.SVGRenderer#init
  2314. *
  2315. * @param {Highcharts.HTMLDOMElement} container
  2316. * Where to put the SVG in the web page.
  2317. *
  2318. * @param {number} width
  2319. * The width of the SVG.
  2320. *
  2321. * @param {number} height
  2322. * The height of the SVG.
  2323. *
  2324. * @param {boolean} [forExport=false]
  2325. * Whether the rendered content is intended for export.
  2326. *
  2327. * @param {boolean} [allowHTML=true]
  2328. * Whether the renderer is allowed to include HTML text, which will
  2329. * be projected on top of the SVG.
  2330. *
  2331. * @param {boolean} [styledMode=false]
  2332. * Whether the renderer belongs to a chart that is in styled mode.
  2333. * If it does, it will avoid setting presentational attributes in
  2334. * some cases, but not when set explicitly through `.attr` and `.css`
  2335. * etc.
  2336. *
  2337. * @return {void}
  2338. */
  2339. init: function (
  2340. container,
  2341. width,
  2342. height,
  2343. style,
  2344. forExport,
  2345. allowHTML,
  2346. styledMode
  2347. ) {
  2348. var renderer = this,
  2349. boxWrapper,
  2350. element,
  2351. desc;
  2352. boxWrapper = renderer.createElement('svg')
  2353. .attr({
  2354. 'version': '1.1',
  2355. 'class': 'highcharts-root'
  2356. });
  2357. if (!styledMode) {
  2358. boxWrapper.css(this.getStyle(style));
  2359. }
  2360. element = boxWrapper.element;
  2361. container.appendChild(element);
  2362. // Always use ltr on the container, otherwise text-anchor will be
  2363. // flipped and text appear outside labels, buttons, tooltip etc (#3482)
  2364. attr(container, 'dir', 'ltr');
  2365. // For browsers other than IE, add the namespace attribute (#1978)
  2366. if (container.innerHTML.indexOf('xmlns') === -1) {
  2367. attr(element, 'xmlns', this.SVG_NS);
  2368. }
  2369. // object properties
  2370. renderer.isSVG = true;
  2371. /**
  2372. * The root `svg` node of the renderer.
  2373. *
  2374. * @name Highcharts.SVGRenderer#box
  2375. * @type {Highcharts.SVGDOMElement}
  2376. */
  2377. this.box = element;
  2378. /**
  2379. * The wrapper for the root `svg` node of the renderer.
  2380. *
  2381. * @name Highcharts.SVGRenderer#boxWrapper
  2382. * @type {Highcharts.SVGElement}
  2383. */
  2384. this.boxWrapper = boxWrapper;
  2385. renderer.alignedObjects = [];
  2386. /**
  2387. * Page url used for internal references.
  2388. *
  2389. * @private
  2390. * @name Highcharts.SVGRenderer#url
  2391. * @type {string}
  2392. */
  2393. // #24, #672, #1070
  2394. this.url = (
  2395. (isFirefox || isWebKit) &&
  2396. doc.getElementsByTagName('base').length
  2397. ) ?
  2398. win.location.href
  2399. .split('#')[0] // remove the hash
  2400. .replace(/<[^>]*>/g, '') // wing cut HTML
  2401. // escape parantheses and quotes
  2402. .replace(/([\('\)])/g, '\\$1')
  2403. // replace spaces (needed for Safari only)
  2404. .replace(/ /g, '%20') :
  2405. '';
  2406. // Add description
  2407. desc = this.createElement('desc').add();
  2408. desc.element.appendChild(
  2409. doc.createTextNode('Created with @product.name@ @product.version@')
  2410. );
  2411. /**
  2412. * A pointer to the `defs` node of the root SVG.
  2413. *
  2414. * @name Highcharts.SVGRenderer#defs
  2415. * @type {Highcharts.SVGElement}
  2416. */
  2417. renderer.defs = this.createElement('defs').add();
  2418. renderer.allowHTML = allowHTML;
  2419. renderer.forExport = forExport;
  2420. renderer.styledMode = styledMode;
  2421. renderer.gradients = {}; // Object where gradient SvgElements are stored
  2422. renderer.cache = {}; // Cache for numerical bounding boxes
  2423. renderer.cacheKeys = [];
  2424. renderer.imgCount = 0;
  2425. renderer.setSize(width, height, false);
  2426. // Issue 110 workaround:
  2427. // In Firefox, if a div is positioned by percentage, its pixel position
  2428. // may land between pixels. The container itself doesn't display this,
  2429. // but an SVG element inside this container will be drawn at subpixel
  2430. // precision. In order to draw sharp lines, this must be compensated
  2431. // for. This doesn't seem to work inside iframes though (like in
  2432. // jsFiddle).
  2433. var subPixelFix, rect;
  2434. if (isFirefox && container.getBoundingClientRect) {
  2435. subPixelFix = function () {
  2436. css(container, { left: 0, top: 0 });
  2437. rect = container.getBoundingClientRect();
  2438. css(container, {
  2439. left: (Math.ceil(rect.left) - rect.left) + 'px',
  2440. top: (Math.ceil(rect.top) - rect.top) + 'px'
  2441. });
  2442. };
  2443. // run the fix now
  2444. subPixelFix();
  2445. // run it on resize
  2446. renderer.unSubPixelFix = addEvent(win, 'resize', subPixelFix);
  2447. }
  2448. },
  2449. /**
  2450. * General method for adding a definition to the SVG `defs` tag. Can be used
  2451. * for gradients, fills, filters etc. Styled mode only. A hook for adding
  2452. * general definitions to the SVG's defs tag. Definitions can be referenced
  2453. * from the CSS by its `id`. Read more in
  2454. * [gradients, shadows and patterns](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns).
  2455. * Styled mode only.
  2456. *
  2457. * @function Highcharts.SVGRenderer#definition
  2458. *
  2459. * @param {Highcharts.SVGDefinitionObject} def
  2460. * A serialized form of an SVG definition, including children.
  2461. *
  2462. * @return {Highcharts.SVGElement}
  2463. * The inserted node.
  2464. */
  2465. definition: function (def) {
  2466. var ren = this;
  2467. function recurse(config, parent) {
  2468. var ret;
  2469. splat(config).forEach(function (item) {
  2470. var node = ren.createElement(item.tagName),
  2471. attr = {};
  2472. // Set attributes
  2473. objectEach(item, function (val, key) {
  2474. if (
  2475. key !== 'tagName' &&
  2476. key !== 'children' &&
  2477. key !== 'textContent'
  2478. ) {
  2479. attr[key] = val;
  2480. }
  2481. });
  2482. node.attr(attr);
  2483. // Add to the tree
  2484. node.add(parent || ren.defs);
  2485. // Add text content
  2486. if (item.textContent) {
  2487. node.element.appendChild(
  2488. doc.createTextNode(item.textContent)
  2489. );
  2490. }
  2491. // Recurse
  2492. recurse(item.children || [], node);
  2493. ret = node;
  2494. });
  2495. // Return last node added (on top level it's the only one)
  2496. return ret;
  2497. }
  2498. return recurse(def);
  2499. },
  2500. /**
  2501. * Get the global style setting for the renderer.
  2502. *
  2503. * @private
  2504. * @function Highcharts.SVGRenderer#getStyle
  2505. *
  2506. * @param {Highcharts.CSSObject} style
  2507. * Style settings.
  2508. *
  2509. * @return {Highcharts.CSSObject}
  2510. * The style settings mixed with defaults.
  2511. */
  2512. getStyle: function (style) {
  2513. this.style = extend({
  2514. fontFamily: '"Lucida Grande", "Lucida Sans Unicode", ' +
  2515. 'Arial, Helvetica, sans-serif',
  2516. fontSize: '12px'
  2517. }, style);
  2518. return this.style;
  2519. },
  2520. /**
  2521. * Apply the global style on the renderer, mixed with the default styles.
  2522. *
  2523. * @function Highcharts.SVGRenderer#setStyle
  2524. *
  2525. * @param {Highcharts.CSSObject} style
  2526. * CSS to apply.
  2527. */
  2528. setStyle: function (style) {
  2529. this.boxWrapper.css(this.getStyle(style));
  2530. },
  2531. /**
  2532. * Detect whether the renderer is hidden. This happens when one of the
  2533. * parent elements has `display: none`. Used internally to detect when we
  2534. * needto render preliminarily in another div to get the text bounding boxes
  2535. * right.
  2536. *
  2537. * @function Highcharts.SVGRenderer#isHidden
  2538. *
  2539. * @return {boolean}
  2540. * True if it is hidden.
  2541. */
  2542. isHidden: function () { // #608
  2543. return !this.boxWrapper.getBBox().width;
  2544. },
  2545. /**
  2546. * Destroys the renderer and its allocated members.
  2547. *
  2548. * @function Highcharts.SVGRenderer#destroy
  2549. */
  2550. destroy: function () {
  2551. var renderer = this,
  2552. rendererDefs = renderer.defs;
  2553. renderer.box = null;
  2554. renderer.boxWrapper = renderer.boxWrapper.destroy();
  2555. // Call destroy on all gradient elements
  2556. destroyObjectProperties(renderer.gradients || {});
  2557. renderer.gradients = null;
  2558. // Defs are null in VMLRenderer
  2559. // Otherwise, destroy them here.
  2560. if (rendererDefs) {
  2561. renderer.defs = rendererDefs.destroy();
  2562. }
  2563. // Remove sub pixel fix handler (#982)
  2564. if (renderer.unSubPixelFix) {
  2565. renderer.unSubPixelFix();
  2566. }
  2567. renderer.alignedObjects = null;
  2568. return null;
  2569. },
  2570. /**
  2571. * Create a wrapper for an SVG element. Serves as a factory for
  2572. * {@link SVGElement}, but this function is itself mostly called from
  2573. * primitive factories like {@link SVGRenderer#path}, {@link
  2574. * SVGRenderer#rect} or {@link SVGRenderer#text}.
  2575. *
  2576. * @function Highcharts.SVGRenderer#createElement
  2577. *
  2578. * @param {string} nodeName
  2579. * The node name, for example `rect`, `g` etc.
  2580. *
  2581. * @return {Highcharts.SVGElement}
  2582. * The generated SVGElement.
  2583. */
  2584. createElement: function (nodeName) {
  2585. var wrapper = new this.Element();
  2586. wrapper.init(this, nodeName);
  2587. return wrapper;
  2588. },
  2589. /**
  2590. * Dummy function for plugins, called every time the renderer is updated.
  2591. * Prior to Highcharts 5, this was used for the canvg renderer.
  2592. *
  2593. * @deprecated
  2594. * @function Highcharts.SVGRenderer#draw
  2595. */
  2596. draw: noop,
  2597. /**
  2598. * Get converted radial gradient attributes according to the radial
  2599. * reference. Used internally from the {@link SVGElement#colorGradient}
  2600. * function.
  2601. *
  2602. * @private
  2603. * @function Highcharts.SVGRenderer#getRadialAttr
  2604. *
  2605. * @param {Array<number>} radialReference
  2606. *
  2607. * @param {Highcharts.SVGAttributes} gradAttr
  2608. *
  2609. * @return {Highcharts.SVGAttributes}
  2610. */
  2611. getRadialAttr: function (radialReference, gradAttr) {
  2612. return {
  2613. cx: (radialReference[0] - radialReference[2] / 2) +
  2614. gradAttr.cx * radialReference[2],
  2615. cy: (radialReference[1] - radialReference[2] / 2) +
  2616. gradAttr.cy * radialReference[2],
  2617. r: gradAttr.r * radialReference[2]
  2618. };
  2619. },
  2620. /**
  2621. * Truncate the text node contents to a given length. Used when the css
  2622. * width is set. If the `textOverflow` is `ellipsis`, the text is truncated
  2623. * character by character to the given length. If not, the text is
  2624. * word-wrapped line by line.
  2625. *
  2626. * @private
  2627. * @function Highcharts.SVGRenderer#truncate
  2628. *
  2629. * @param {Highcharts.SVGElement} wrapper
  2630. *
  2631. * @param {Highcharts.SVGDOMElement} tspan
  2632. *
  2633. * @param {string} text
  2634. *
  2635. * @param {Array.<string>} words
  2636. *
  2637. * @param {number} width
  2638. *
  2639. * @param {Function} getString
  2640. *
  2641. * @return {boolean}
  2642. * True if tspan is too long.
  2643. */
  2644. truncate: function (
  2645. wrapper,
  2646. tspan,
  2647. text,
  2648. words,
  2649. startAt,
  2650. width,
  2651. getString
  2652. ) {
  2653. var renderer = this,
  2654. rotation = wrapper.rotation,
  2655. str,
  2656. // Word wrap can not be truncated to shorter than one word, ellipsis
  2657. // text can be completely blank.
  2658. minIndex = words ? 1 : 0,
  2659. maxIndex = (text || words).length,
  2660. currentIndex = maxIndex,
  2661. // Cache the lengths to avoid checking the same twice
  2662. lengths = [],
  2663. updateTSpan = function (s) {
  2664. if (tspan.firstChild) {
  2665. tspan.removeChild(tspan.firstChild);
  2666. }
  2667. if (s) {
  2668. tspan.appendChild(doc.createTextNode(s));
  2669. }
  2670. },
  2671. getSubStringLength = function (charEnd, concatenatedEnd) {
  2672. // charEnd is useed when finding the character-by-character
  2673. // break for ellipsis, concatenatedEnd is used for word-by-word
  2674. // break for word wrapping.
  2675. var end = concatenatedEnd || charEnd;
  2676. if (lengths[end] === undefined) {
  2677. // Modern browsers
  2678. if (tspan.getSubStringLength) {
  2679. // Fails with DOM exception on unit-tests/legend/members
  2680. // of unknown reason. Desired width is 0, text content
  2681. // is "5" and end is 1.
  2682. try {
  2683. lengths[end] = startAt + tspan.getSubStringLength(
  2684. 0,
  2685. words ? end + 1 : end
  2686. );
  2687. } catch (e) {}
  2688. // Legacy
  2689. } else if (renderer.getSpanWidth) { // #9058 jsdom
  2690. updateTSpan(getString(text || words, charEnd));
  2691. lengths[end] = startAt +
  2692. renderer.getSpanWidth(wrapper, tspan);
  2693. }
  2694. }
  2695. return lengths[end];
  2696. },
  2697. actualWidth,
  2698. truncated;
  2699. wrapper.rotation = 0; // discard rotation when computing box
  2700. actualWidth = getSubStringLength(tspan.textContent.length);
  2701. truncated = startAt + actualWidth > width;
  2702. if (truncated) {
  2703. // Do a binary search for the index where to truncate the text
  2704. while (minIndex <= maxIndex) {
  2705. currentIndex = Math.ceil((minIndex + maxIndex) / 2);
  2706. // When checking words for word-wrap, we need to build the
  2707. // string and measure the subStringLength at the concatenated
  2708. // word length.
  2709. if (words) {
  2710. str = getString(words, currentIndex);
  2711. }
  2712. actualWidth = getSubStringLength(
  2713. currentIndex,
  2714. str && str.length - 1
  2715. );
  2716. if (minIndex === maxIndex) {
  2717. // Complete
  2718. minIndex = maxIndex + 1;
  2719. } else if (actualWidth > width) {
  2720. // Too large. Set max index to current.
  2721. maxIndex = currentIndex - 1;
  2722. } else {
  2723. // Within width. Set min index to current.
  2724. minIndex = currentIndex;
  2725. }
  2726. }
  2727. // If max index was 0 it means the shortest possible text was also
  2728. // too large. For ellipsis that means only the ellipsis, while for
  2729. // word wrap it means the whole first word.
  2730. if (maxIndex === 0) {
  2731. // Remove ellipsis
  2732. updateTSpan('');
  2733. // If the new text length is one less than the original, we don't
  2734. // need the ellipsis
  2735. } else if (!(text && maxIndex === text.length - 1)) {
  2736. updateTSpan(str || getString(text || words, currentIndex));
  2737. }
  2738. }
  2739. // When doing line wrapping, prepare for the next line by removing the
  2740. // items from this line.
  2741. if (words) {
  2742. words.splice(0, currentIndex);
  2743. }
  2744. wrapper.actualWidth = actualWidth;
  2745. wrapper.rotation = rotation; // Apply rotation again.
  2746. return truncated;
  2747. },
  2748. /**
  2749. * A collection of characters mapped to HTML entities. When `useHTML` on an
  2750. * element is true, these entities will be rendered correctly by HTML. In
  2751. * the SVG pseudo-HTML, they need to be unescaped back to simple characters,
  2752. * so for example `&lt;` will render as `<`.
  2753. *
  2754. * @example
  2755. * // Add support for unescaping quotes
  2756. * Highcharts.SVGRenderer.prototype.escapes['"'] = '&quot;';
  2757. *
  2758. * @name Highcharts.SVGRenderer#escapes
  2759. * @type {Highcharts.Dictionary<string>}
  2760. */
  2761. escapes: {
  2762. '&': '&amp;',
  2763. '<': '&lt;',
  2764. '>': '&gt;',
  2765. "'": '&#39;', // eslint-disable-line quotes
  2766. '"': '&quot;'
  2767. },
  2768. /**
  2769. * Parse a simple HTML string into SVG tspans. Called internally when text
  2770. * is set on an SVGElement. The function supports a subset of HTML tags, CSS
  2771. * text features like `width`, `text-overflow`, `white-space`, and also
  2772. * attributes like `href` and `style`.
  2773. *
  2774. * @private
  2775. * @function Highcharts.SVGRenderer#buildText
  2776. *
  2777. * @param {Highcharts.SVGElement} wrapper
  2778. * The parent SVGElement.
  2779. */
  2780. buildText: function (wrapper) {
  2781. var textNode = wrapper.element,
  2782. renderer = this,
  2783. forExport = renderer.forExport,
  2784. textStr = pick(wrapper.textStr, '').toString(),
  2785. hasMarkup = textStr.indexOf('<') !== -1,
  2786. lines,
  2787. childNodes = textNode.childNodes,
  2788. truncated,
  2789. parentX = attr(textNode, 'x'),
  2790. textStyles = wrapper.styles,
  2791. width = wrapper.textWidth,
  2792. textLineHeight = textStyles && textStyles.lineHeight,
  2793. textOutline = textStyles && textStyles.textOutline,
  2794. ellipsis = textStyles && textStyles.textOverflow === 'ellipsis',
  2795. noWrap = textStyles && textStyles.whiteSpace === 'nowrap',
  2796. fontSize = textStyles && textStyles.fontSize,
  2797. textCache,
  2798. isSubsequentLine,
  2799. i = childNodes.length,
  2800. tempParent = width && !wrapper.added && this.box,
  2801. getLineHeight = function (tspan) {
  2802. var fontSizeStyle;
  2803. if (!renderer.styledMode) {
  2804. fontSizeStyle =
  2805. /(px|em)$/.test(tspan && tspan.style.fontSize) ?
  2806. tspan.style.fontSize :
  2807. (fontSize || renderer.style.fontSize || 12);
  2808. }
  2809. return textLineHeight ?
  2810. pInt(textLineHeight) :
  2811. renderer.fontMetrics(
  2812. fontSizeStyle,
  2813. // Get the computed size from parent if not explicit
  2814. tspan.getAttribute('style') ? tspan : textNode
  2815. ).h;
  2816. },
  2817. unescapeEntities = function (inputStr, except) {
  2818. objectEach(renderer.escapes, function (value, key) {
  2819. if (!except || except.indexOf(value) === -1) {
  2820. inputStr = inputStr.toString().replace(
  2821. new RegExp(value, 'g'), // eslint-disable-line security/detect-non-literal-regexp
  2822. key
  2823. );
  2824. }
  2825. });
  2826. return inputStr;
  2827. },
  2828. parseAttribute = function (s, attr) {
  2829. var start,
  2830. delimiter;
  2831. start = s.indexOf('<');
  2832. s = s.substring(start, s.indexOf('>') - start);
  2833. start = s.indexOf(attr + '=');
  2834. if (start !== -1) {
  2835. start = start + attr.length + 1;
  2836. delimiter = s.charAt(start);
  2837. if (delimiter === '"' || delimiter === "'") { // eslint-disable-line quotes
  2838. s = s.substring(start + 1);
  2839. return s.substring(0, s.indexOf(delimiter));
  2840. }
  2841. }
  2842. };
  2843. // The buildText code is quite heavy, so if we're not changing something
  2844. // that affects the text, skip it (#6113).
  2845. textCache = [
  2846. textStr,
  2847. ellipsis,
  2848. noWrap,
  2849. textLineHeight,
  2850. textOutline,
  2851. fontSize,
  2852. width
  2853. ].join(',');
  2854. if (textCache === wrapper.textCache) {
  2855. return;
  2856. }
  2857. wrapper.textCache = textCache;
  2858. // Remove old text
  2859. while (i--) {
  2860. textNode.removeChild(childNodes[i]);
  2861. }
  2862. // Skip tspans, add text directly to text node. The forceTSpan is a hook
  2863. // used in text outline hack.
  2864. if (
  2865. !hasMarkup &&
  2866. !textOutline &&
  2867. !ellipsis &&
  2868. !width &&
  2869. textStr.indexOf(' ') === -1
  2870. ) {
  2871. textNode.appendChild(doc.createTextNode(unescapeEntities(textStr)));
  2872. // Complex strings, add more logic
  2873. } else {
  2874. if (tempParent) {
  2875. // attach it to the DOM to read offset width
  2876. tempParent.appendChild(textNode);
  2877. }
  2878. if (hasMarkup) {
  2879. lines = renderer.styledMode ? (
  2880. textStr
  2881. .replace(
  2882. /<(b|strong)>/g,
  2883. '<span class="highcharts-strong">'
  2884. )
  2885. .replace(
  2886. /<(i|em)>/g,
  2887. '<span class="highcharts-emphasized">'
  2888. )
  2889. ) : (
  2890. textStr
  2891. .replace(
  2892. /<(b|strong)>/g,
  2893. '<span style="font-weight:bold">'
  2894. )
  2895. .replace(
  2896. /<(i|em)>/g,
  2897. '<span style="font-style:italic">'
  2898. )
  2899. );
  2900. lines = lines
  2901. .replace(/<a/g, '<span')
  2902. .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
  2903. .split(/<br.*?>/g);
  2904. } else {
  2905. lines = [textStr];
  2906. }
  2907. // Trim empty lines (#5261)
  2908. lines = lines.filter(function (line) {
  2909. return line !== '';
  2910. });
  2911. // build the lines
  2912. lines.forEach(function buildTextLines(line, lineNo) {
  2913. var spans,
  2914. spanNo = 0,
  2915. lineLength = 0;
  2916. line = line
  2917. // Trim to prevent useless/costly process on the spaces
  2918. // (#5258)
  2919. .replace(/^\s+|\s+$/g, '')
  2920. .replace(/<span/g, '|||<span')
  2921. .replace(/<\/span>/g, '</span>|||');
  2922. spans = line.split('|||');
  2923. spans.forEach(function buildTextSpans(span) {
  2924. if (span !== '' || spans.length === 1) {
  2925. var attributes = {},
  2926. tspan = doc.createElementNS(
  2927. renderer.SVG_NS,
  2928. 'tspan'
  2929. ),
  2930. classAttribute,
  2931. styleAttribute, // #390
  2932. hrefAttribute;
  2933. classAttribute = parseAttribute(span, 'class');
  2934. if (classAttribute) {
  2935. attr(tspan, 'class', classAttribute);
  2936. }
  2937. styleAttribute = parseAttribute(span, 'style');
  2938. if (styleAttribute) {
  2939. styleAttribute = styleAttribute.replace(
  2940. /(;| |^)color([ :])/,
  2941. '$1fill$2'
  2942. );
  2943. attr(tspan, 'style', styleAttribute);
  2944. }
  2945. // Not for export - #1529
  2946. hrefAttribute = parseAttribute(span, 'href');
  2947. if (hrefAttribute && !forExport) {
  2948. attr(
  2949. tspan,
  2950. 'onclick',
  2951. 'location.href=\"' + hrefAttribute + '\"'
  2952. );
  2953. attr(tspan, 'class', 'highcharts-anchor');
  2954. if (!renderer.styledMode) {
  2955. css(tspan, { cursor: 'pointer' });
  2956. }
  2957. }
  2958. // Strip away unsupported HTML tags (#7126)
  2959. span = unescapeEntities(
  2960. span.replace(/<[a-zA-Z\/](.|\n)*?>/g, '') || ' '
  2961. );
  2962. // Nested tags aren't supported, and cause crash in
  2963. // Safari (#1596)
  2964. if (span !== ' ') {
  2965. // add the text node
  2966. tspan.appendChild(doc.createTextNode(span));
  2967. // First span in a line, align it to the left
  2968. if (!spanNo) {
  2969. if (lineNo && parentX !== null) {
  2970. attributes.x = parentX;
  2971. }
  2972. } else {
  2973. attributes.dx = 0; // #16
  2974. }
  2975. // add attributes
  2976. attr(tspan, attributes);
  2977. // Append it
  2978. textNode.appendChild(tspan);
  2979. // first span on subsequent line, add the line
  2980. // height
  2981. if (!spanNo && isSubsequentLine) {
  2982. // allow getting the right offset height in
  2983. // exporting in IE
  2984. if (!svg && forExport) {
  2985. css(tspan, { display: 'block' });
  2986. }
  2987. // Set the line height based on the font size of
  2988. // either the text element or the tspan element
  2989. attr(
  2990. tspan,
  2991. 'dy',
  2992. getLineHeight(tspan)
  2993. );
  2994. }
  2995. // Check width and apply soft breaks or ellipsis
  2996. if (width) {
  2997. var words = span.replace(
  2998. /([^\^])-/g,
  2999. '$1- '
  3000. ).split(' '), // #1273
  3001. hasWhiteSpace = !noWrap && (
  3002. spans.length > 1 ||
  3003. lineNo ||
  3004. words.length > 1
  3005. ),
  3006. wrapLineNo = 0,
  3007. dy = getLineHeight(tspan);
  3008. if (ellipsis) {
  3009. truncated = renderer.truncate(
  3010. wrapper,
  3011. tspan,
  3012. span,
  3013. undefined,
  3014. 0,
  3015. // Target width
  3016. Math.max(
  3017. 0,
  3018. // Substract the font face to make
  3019. // room for the ellipsis itself
  3020. width - parseInt(fontSize || 12, 10)
  3021. ),
  3022. // Build the text to test for
  3023. function (text, currentIndex) {
  3024. return text.substring(
  3025. 0,
  3026. currentIndex
  3027. ) + '\u2026';
  3028. }
  3029. );
  3030. } else if (hasWhiteSpace) {
  3031. while (words.length) {
  3032. // For subsequent lines, create tspans
  3033. // with the same style attributes as the
  3034. // parent text node.
  3035. if (
  3036. words.length &&
  3037. !noWrap &&
  3038. wrapLineNo > 0
  3039. ) {
  3040. tspan = doc.createElementNS(
  3041. SVG_NS,
  3042. 'tspan'
  3043. );
  3044. attr(tspan, {
  3045. dy: dy,
  3046. x: parentX
  3047. });
  3048. if (styleAttribute) { // #390
  3049. attr(
  3050. tspan,
  3051. 'style',
  3052. styleAttribute
  3053. );
  3054. }
  3055. // Start by appending the full
  3056. // remaining text
  3057. tspan.appendChild(
  3058. doc.createTextNode(
  3059. words.join(' ')
  3060. .replace(/- /g, '-')
  3061. )
  3062. );
  3063. textNode.appendChild(tspan);
  3064. }
  3065. // For each line, truncate the remaining
  3066. // words into the line length.
  3067. renderer.truncate(
  3068. wrapper,
  3069. tspan,
  3070. null,
  3071. words,
  3072. wrapLineNo === 0 ? lineLength : 0,
  3073. width,
  3074. // Build the text to test for
  3075. function (text, currentIndex) {
  3076. return words
  3077. .slice(0, currentIndex)
  3078. .join(' ')
  3079. .replace(/- /g, '-');
  3080. }
  3081. );
  3082. lineLength = wrapper.actualWidth;
  3083. wrapLineNo++;
  3084. }
  3085. }
  3086. }
  3087. spanNo++;
  3088. }
  3089. }
  3090. });
  3091. // To avoid beginning lines that doesn't add to the textNode
  3092. // (#6144)
  3093. isSubsequentLine = (
  3094. isSubsequentLine ||
  3095. textNode.childNodes.length
  3096. );
  3097. });
  3098. if (ellipsis && truncated) {
  3099. wrapper.attr(
  3100. 'title',
  3101. unescapeEntities(wrapper.textStr, ['&lt;', '&gt;']) // #7179
  3102. );
  3103. }
  3104. if (tempParent) {
  3105. tempParent.removeChild(textNode);
  3106. }
  3107. // Apply the text outline
  3108. if (textOutline && wrapper.applyTextOutline) {
  3109. wrapper.applyTextOutline(textOutline);
  3110. }
  3111. }
  3112. },
  3113. /**
  3114. * Returns white for dark colors and black for bright colors.
  3115. *
  3116. * @function Highcharts.SVGRenderer#getContrast
  3117. *
  3118. * @param {Highcharts.ColorString} rgba
  3119. * The color to get the contrast for.
  3120. *
  3121. * @return {string}
  3122. * The contrast color, either `#000000` or `#FFFFFF`.
  3123. */
  3124. getContrast: function (rgba) {
  3125. rgba = color(rgba).rgba;
  3126. // The threshold may be discussed. Here's a proposal for adding
  3127. // different weight to the color channels (#6216)
  3128. rgba[0] *= 1; // red
  3129. rgba[1] *= 1.2; // green
  3130. rgba[2] *= 0.5; // blue
  3131. return rgba[0] + rgba[1] + rgba[2] > 1.8 * 255 ? '#000000' : '#FFFFFF';
  3132. },
  3133. /**
  3134. * Create a button with preset states.
  3135. *
  3136. * @function Highcharts.SVGRenderer#button
  3137. *
  3138. * @param {string} text
  3139. * The text or HTML to draw.
  3140. *
  3141. * @param {number} x
  3142. * The x position of the button's left side.
  3143. *
  3144. * @param {number} y
  3145. * The y position of the button's top side.
  3146. *
  3147. * @param {Function} callback
  3148. * The function to execute on button click or touch.
  3149. *
  3150. * @param {Highcharts.SVGAttributes} [normalState]
  3151. * SVG attributes for the normal state.
  3152. *
  3153. * @param {Highcharts.SVGAttributes} [hoverState]
  3154. * SVG attributes for the hover state.
  3155. *
  3156. * @param {Highcharts.SVGAttributes} [pressedState]
  3157. * SVG attributes for the pressed state.
  3158. *
  3159. * @param {Highcharts.SVGAttributes} [disabledState]
  3160. * SVG attributes for the disabled state.
  3161. *
  3162. * @param {Highcharts.SymbolKey} [shape=rect]
  3163. * The shape type.
  3164. *
  3165. * @return {Highcharts.SVGElement}
  3166. * The button element.
  3167. */
  3168. button: function (
  3169. text,
  3170. x,
  3171. y,
  3172. callback,
  3173. normalState,
  3174. hoverState,
  3175. pressedState,
  3176. disabledState,
  3177. shape
  3178. ) {
  3179. var label = this.label(
  3180. text,
  3181. x,
  3182. y,
  3183. shape,
  3184. null,
  3185. null,
  3186. null,
  3187. null,
  3188. 'button'
  3189. ),
  3190. curState = 0,
  3191. styledMode = this.styledMode;
  3192. // Default, non-stylable attributes
  3193. label.attr(merge({
  3194. 'padding': 8,
  3195. 'r': 2
  3196. }, normalState));
  3197. if (!styledMode) {
  3198. // Presentational
  3199. var normalStyle,
  3200. hoverStyle,
  3201. pressedStyle,
  3202. disabledStyle;
  3203. // Normal state - prepare the attributes
  3204. normalState = merge({
  3205. fill: '#f7f7f7',
  3206. stroke: '#cccccc',
  3207. 'stroke-width': 1,
  3208. style: {
  3209. color: '#333333',
  3210. cursor: 'pointer',
  3211. fontWeight: 'normal'
  3212. }
  3213. }, normalState);
  3214. normalStyle = normalState.style;
  3215. delete normalState.style;
  3216. // Hover state
  3217. hoverState = merge(normalState, {
  3218. fill: '#e6e6e6'
  3219. }, hoverState);
  3220. hoverStyle = hoverState.style;
  3221. delete hoverState.style;
  3222. // Pressed state
  3223. pressedState = merge(normalState, {
  3224. fill: '#e6ebf5',
  3225. style: {
  3226. color: '#000000',
  3227. fontWeight: 'bold'
  3228. }
  3229. }, pressedState);
  3230. pressedStyle = pressedState.style;
  3231. delete pressedState.style;
  3232. // Disabled state
  3233. disabledState = merge(normalState, {
  3234. style: {
  3235. color: '#cccccc'
  3236. }
  3237. }, disabledState);
  3238. disabledStyle = disabledState.style;
  3239. delete disabledState.style;
  3240. }
  3241. // Add the events. IE9 and IE10 need mouseover and mouseout to funciton
  3242. // (#667).
  3243. addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function () {
  3244. if (curState !== 3) {
  3245. label.setState(1);
  3246. }
  3247. });
  3248. addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function () {
  3249. if (curState !== 3) {
  3250. label.setState(curState);
  3251. }
  3252. });
  3253. label.setState = function (state) {
  3254. // Hover state is temporary, don't record it
  3255. if (state !== 1) {
  3256. label.state = curState = state;
  3257. }
  3258. // Update visuals
  3259. label
  3260. .removeClass(
  3261. /highcharts-button-(normal|hover|pressed|disabled)/
  3262. )
  3263. .addClass(
  3264. 'highcharts-button-' +
  3265. ['normal', 'hover', 'pressed', 'disabled'][state || 0]
  3266. );
  3267. if (!styledMode) {
  3268. label
  3269. .attr([
  3270. normalState,
  3271. hoverState,
  3272. pressedState,
  3273. disabledState
  3274. ][state || 0])
  3275. .css([
  3276. normalStyle,
  3277. hoverStyle,
  3278. pressedStyle,
  3279. disabledStyle
  3280. ][state || 0]);
  3281. }
  3282. };
  3283. // Presentational attributes
  3284. if (!styledMode) {
  3285. label
  3286. .attr(normalState)
  3287. .css(extend({ cursor: 'default' }, normalStyle));
  3288. }
  3289. return label
  3290. .on('click', function (e) {
  3291. if (curState !== 3) {
  3292. callback.call(label, e);
  3293. }
  3294. });
  3295. },
  3296. /**
  3297. * Make a straight line crisper by not spilling out to neighbour pixels.
  3298. *
  3299. * @function Highcharts.SVGRenderer#crispLine
  3300. *
  3301. * @param {Highcharts.SVGPathArray} points
  3302. * The original points on the format `['M', 0, 0, 'L', 100, 0]`.
  3303. *
  3304. * @param {number} width
  3305. * The width of the line.
  3306. *
  3307. * @return {Highcharts.SVGPathArray}
  3308. * The original points array, but modified to render crisply.
  3309. */
  3310. crispLine: function (points, width) {
  3311. // normalize to a crisp line
  3312. if (points[1] === points[4]) {
  3313. // Substract due to #1129. Now bottom and left axis gridlines behave
  3314. // the same.
  3315. points[1] = points[4] = Math.round(points[1]) - (width % 2 / 2);
  3316. }
  3317. if (points[2] === points[5]) {
  3318. points[2] = points[5] = Math.round(points[2]) + (width % 2 / 2);
  3319. }
  3320. return points;
  3321. },
  3322. /**
  3323. * Draw a path, wraps the SVG `path` element.
  3324. *
  3325. * @sample highcharts/members/renderer-path-on-chart/
  3326. * Draw a path in a chart
  3327. * @sample highcharts/members/renderer-path/
  3328. * Draw a path independent from a chart
  3329. *
  3330. * @example
  3331. * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z'])
  3332. * .attr({ stroke: '#ff00ff' })
  3333. * .add();
  3334. *
  3335. * @function Highcharts.SVGRenderer#path
  3336. *
  3337. * @param {Highcharts.SVGPathArray} [path]
  3338. * An SVG path definition in array form.
  3339. *
  3340. * @return {Highcharts.SVGElement}
  3341. * The generated wrapper element.
  3342. *
  3343. *//**
  3344. * Draw a path, wraps the SVG `path` element.
  3345. *
  3346. * @function Highcharts.SVGRenderer#path
  3347. *
  3348. * @param {Highcharts.SVGAttributes} [attribs]
  3349. * The initial attributes.
  3350. *
  3351. * @return {Highcharts.SVGElement}
  3352. * The generated wrapper element.
  3353. */
  3354. path: function (path) {
  3355. var attribs = this.styledMode ? {} : {
  3356. fill: 'none'
  3357. };
  3358. if (isArray(path)) {
  3359. attribs.d = path;
  3360. } else if (isObject(path)) { // attributes
  3361. extend(attribs, path);
  3362. }
  3363. return this.createElement('path').attr(attribs);
  3364. },
  3365. /**
  3366. * Draw a circle, wraps the SVG `circle` element.
  3367. *
  3368. * @sample highcharts/members/renderer-circle/
  3369. * Drawing a circle
  3370. *
  3371. * @function Highcharts.SVGRenderer#circle
  3372. *
  3373. * @param {number} [x]
  3374. * The center x position.
  3375. *
  3376. * @param {number} [y]
  3377. * The center y position.
  3378. *
  3379. * @param {number} [r]
  3380. * The radius.
  3381. *
  3382. * @return {Highcharts.SVGElement}
  3383. * The generated wrapper element.
  3384. *//**
  3385. * Draw a circle, wraps the SVG `circle` element.
  3386. *
  3387. * @function Highcharts.SVGRenderer#circle
  3388. *
  3389. * @param {Highcharts.SVGAttributes} [attribs]
  3390. * The initial attributes.
  3391. *
  3392. * @return {Highcharts.SVGElement}
  3393. * The generated wrapper element.
  3394. */
  3395. circle: function (x, y, r) {
  3396. var attribs = (
  3397. isObject(x) ?
  3398. x :
  3399. x === undefined ? {} : { x: x, y: y, r: r }
  3400. ),
  3401. wrapper = this.createElement('circle');
  3402. // Setting x or y translates to cx and cy
  3403. wrapper.xSetter = wrapper.ySetter = function (value, key, element) {
  3404. element.setAttribute('c' + key, value);
  3405. };
  3406. return wrapper.attr(attribs);
  3407. },
  3408. /**
  3409. * Draw and return an arc.
  3410. *
  3411. * @sample highcharts/members/renderer-arc/
  3412. * Drawing an arc
  3413. *
  3414. * @function Highcharts.SVGRenderer#arc
  3415. *
  3416. * @param {number} [x=0]
  3417. * Center X position.
  3418. *
  3419. * @param {number} [y=0]
  3420. * Center Y position.
  3421. *
  3422. * @param {number} [r=0]
  3423. * The outer radius of the arc.
  3424. *
  3425. * @param {number} [innerR=0]
  3426. * Inner radius like used in donut charts.
  3427. *
  3428. * @param {number} [start=0]
  3429. * The starting angle of the arc in radians, where 0 is to the right
  3430. * and `-Math.PI/2` is up.
  3431. *
  3432. * @param {number} [end=0]
  3433. * The ending angle of the arc in radians, where 0 is to the right
  3434. * and `-Math.PI/2` is up.
  3435. *
  3436. * @return {Highcharts.SVGElement}
  3437. * The generated wrapper element.
  3438. *//**
  3439. * Draw and return an arc. Overloaded function that takes arguments object.
  3440. *
  3441. * @function Highcharts.SVGRenderer#arc
  3442. *
  3443. * @param {Highcharts.SVGAttributes} attribs
  3444. * Initial SVG attributes.
  3445. *
  3446. * @return {Highcharts.SVGElement}
  3447. * The generated wrapper element.
  3448. */
  3449. arc: function (x, y, r, innerR, start, end) {
  3450. var arc,
  3451. options;
  3452. if (isObject(x)) {
  3453. options = x;
  3454. y = options.y;
  3455. r = options.r;
  3456. innerR = options.innerR;
  3457. start = options.start;
  3458. end = options.end;
  3459. x = options.x;
  3460. } else {
  3461. options = {
  3462. innerR: innerR,
  3463. start: start,
  3464. end: end
  3465. };
  3466. }
  3467. // Arcs are defined as symbols for the ability to set
  3468. // attributes in attr and animate
  3469. arc = this.symbol('arc', x, y, r, r, options);
  3470. arc.r = r; // #959
  3471. return arc;
  3472. },
  3473. /**
  3474. * Draw and return a rectangle.
  3475. *
  3476. * @function Highcharts.SVGRenderer#rect
  3477. *
  3478. * @param {number} [x]
  3479. * Left position.
  3480. *
  3481. * @param {number} [y]
  3482. * Top position.
  3483. *
  3484. * @param {number} [width]
  3485. * Width of the rectangle.
  3486. *
  3487. * @param {number} [height]
  3488. * Height of the rectangle.
  3489. *
  3490. * @param {number} [r]
  3491. * Border corner radius.
  3492. *
  3493. * @param {number} [strokeWidth]
  3494. * A stroke width can be supplied to allow crisp drawing.
  3495. *
  3496. * @return {Highcharts.SVGElement}
  3497. * The generated wrapper element.
  3498. *//**
  3499. * Draw and return a rectangle.
  3500. *
  3501. * @sample highcharts/members/renderer-rect-on-chart/
  3502. * Draw a rectangle in a chart
  3503. * @sample highcharts/members/renderer-rect/
  3504. * Draw a rectangle independent from a chart
  3505. *
  3506. * @function Highcharts.SVGRenderer#rect
  3507. *
  3508. * @param {Highcharts.SVGAttributes} [attributes]
  3509. * General SVG attributes for the rectangle.
  3510. *
  3511. * @return {Highcharts.SVGElement}
  3512. * The generated wrapper element.
  3513. */
  3514. rect: function (x, y, width, height, r, strokeWidth) {
  3515. r = isObject(x) ? x.r : r;
  3516. var wrapper = this.createElement('rect'),
  3517. attribs = isObject(x) ? x : x === undefined ? {} : {
  3518. x: x,
  3519. y: y,
  3520. width: Math.max(width, 0),
  3521. height: Math.max(height, 0)
  3522. };
  3523. if (!this.styledMode) {
  3524. if (strokeWidth !== undefined) {
  3525. attribs.strokeWidth = strokeWidth;
  3526. attribs = wrapper.crisp(attribs);
  3527. }
  3528. attribs.fill = 'none';
  3529. }
  3530. if (r) {
  3531. attribs.r = r;
  3532. }
  3533. wrapper.rSetter = function (value, key, element) {
  3534. attr(element, {
  3535. rx: value,
  3536. ry: value
  3537. });
  3538. };
  3539. return wrapper.attr(attribs);
  3540. },
  3541. /**
  3542. * Resize the {@link SVGRenderer#box} and re-align all aligned child
  3543. * elements.
  3544. *
  3545. * @sample highcharts/members/renderer-g/
  3546. * Show and hide grouped objects
  3547. *
  3548. * @function Highcharts.SVGRenderer#setSize
  3549. *
  3550. * @param {number} width
  3551. * The new pixel width.
  3552. *
  3553. * @param {number} height
  3554. * The new pixel height.
  3555. *
  3556. * @param {boolean|Highcharts.AnimationOptionsObject} [animate=true]
  3557. * Whether and how to animate.
  3558. */
  3559. setSize: function (width, height, animate) {
  3560. var renderer = this,
  3561. alignedObjects = renderer.alignedObjects,
  3562. i = alignedObjects.length;
  3563. renderer.width = width;
  3564. renderer.height = height;
  3565. renderer.boxWrapper.animate({
  3566. width: width,
  3567. height: height
  3568. }, {
  3569. step: function () {
  3570. this.attr({
  3571. viewBox: '0 0 ' + this.attr('width') + ' ' +
  3572. this.attr('height')
  3573. });
  3574. },
  3575. duration: pick(animate, true) ? undefined : 0
  3576. });
  3577. while (i--) {
  3578. alignedObjects[i].align();
  3579. }
  3580. },
  3581. /**
  3582. * Create and return an svg group element. Child
  3583. * {@link Highcharts.SVGElement} objects are added to the group by using the
  3584. * group as the first parameter in {@link Highcharts.SVGElement#add|add()}.
  3585. *
  3586. * @function Highcharts.SVGRenderer#g
  3587. *
  3588. * @param {string} [name]
  3589. * The group will be given a class name of `highcharts-{name}`. This
  3590. * can be used for styling and scripting.
  3591. *
  3592. * @return {Highcharts.SVGElement}
  3593. * The generated wrapper element.
  3594. */
  3595. g: function (name) {
  3596. var elem = this.createElement('g');
  3597. return name ? elem.attr({ 'class': 'highcharts-' + name }) : elem;
  3598. },
  3599. /**
  3600. * Display an image.
  3601. *
  3602. * @sample highcharts/members/renderer-image-on-chart/
  3603. * Add an image in a chart
  3604. * @sample highcharts/members/renderer-image/
  3605. * Add an image independent of a chart
  3606. *
  3607. * @function Highcharts.SVGRenderer#image
  3608. *
  3609. * @param {string} src
  3610. * The image source.
  3611. *
  3612. * @param {number} [x]
  3613. * The X position.
  3614. *
  3615. * @param {number} [y]
  3616. * The Y position.
  3617. *
  3618. * @param {number} [width]
  3619. * The image width. If omitted, it defaults to the image file width.
  3620. *
  3621. * @param {number} [height]
  3622. * The image height. If omitted it defaults to the image file
  3623. * height.
  3624. *
  3625. * @param {Function} [onload]
  3626. * Event handler for image load.
  3627. *
  3628. * @return {Highcharts.SVGElement}
  3629. * The generated wrapper element.
  3630. */
  3631. image: function (src, x, y, width, height, onload) {
  3632. var attribs = {
  3633. preserveAspectRatio: 'none'
  3634. },
  3635. elemWrapper,
  3636. dummy,
  3637. setSVGImageSource = function (el, src) {
  3638. // Set the href in the xlink namespace
  3639. if (el.setAttributeNS) {
  3640. el.setAttributeNS(
  3641. 'http://www.w3.org/1999/xlink', 'href', src
  3642. );
  3643. } else {
  3644. // could be exporting in IE
  3645. // using href throws "not supported" in ie7 and under,
  3646. // requries regex shim to fix later
  3647. el.setAttribute('hc-svg-href', src);
  3648. }
  3649. },
  3650. onDummyLoad = function (e) {
  3651. setSVGImageSource(elemWrapper.element, src);
  3652. onload.call(elemWrapper, e);
  3653. };
  3654. // optional properties
  3655. if (arguments.length > 1) {
  3656. extend(attribs, {
  3657. x: x,
  3658. y: y,
  3659. width: width,
  3660. height: height
  3661. });
  3662. }
  3663. elemWrapper = this.createElement('image').attr(attribs);
  3664. // Add load event if supplied
  3665. if (onload) {
  3666. // We have to use a dummy HTML image since IE support for SVG image
  3667. // load events is very buggy. First set a transparent src, wait for
  3668. // dummy to load, and then add the real src to the SVG image.
  3669. setSVGImageSource(
  3670. elemWrapper.element,
  3671. 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' /* eslint-disable-line */
  3672. );
  3673. dummy = new win.Image();
  3674. addEvent(dummy, 'load', onDummyLoad);
  3675. dummy.src = src;
  3676. if (dummy.complete) {
  3677. onDummyLoad({});
  3678. }
  3679. } else {
  3680. setSVGImageSource(elemWrapper.element, src);
  3681. }
  3682. return elemWrapper;
  3683. },
  3684. /**
  3685. * Draw a symbol out of pre-defined shape paths from
  3686. * {@link SVGRenderer#symbols}.
  3687. * It is used in Highcharts for point makers, which cake a `symbol` option,
  3688. * and label and button backgrounds like in the tooltip and stock flags.
  3689. *
  3690. * @function Highcharts.SVGRenderer#symbol
  3691. *
  3692. * @param {symbol} symbol
  3693. * The symbol name.
  3694. *
  3695. * @param {number} x
  3696. * The X coordinate for the top left position.
  3697. *
  3698. * @param {number} y
  3699. * The Y coordinate for the top left position.
  3700. *
  3701. * @param {number} width
  3702. * The pixel width.
  3703. *
  3704. * @param {number} height
  3705. * The pixel height.
  3706. *
  3707. * @param {Highcharts.SymbolOptionsObject} [options]
  3708. * Additional options, depending on the actual symbol drawn.
  3709. *
  3710. * @return {Highcharts.SVGElement}
  3711. */
  3712. symbol: function (symbol, x, y, width, height, options) {
  3713. var ren = this,
  3714. obj,
  3715. imageRegex = /^url\((.*?)\)$/,
  3716. isImage = imageRegex.test(symbol),
  3717. sym = !isImage && (this.symbols[symbol] ? symbol : 'circle'),
  3718. // get the symbol definition function
  3719. symbolFn = sym && this.symbols[sym],
  3720. // check if there's a path defined for this symbol
  3721. path = defined(x) && symbolFn && symbolFn.call(
  3722. this.symbols,
  3723. Math.round(x),
  3724. Math.round(y),
  3725. width,
  3726. height,
  3727. options
  3728. ),
  3729. imageSrc,
  3730. centerImage;
  3731. if (symbolFn) {
  3732. obj = this.path(path);
  3733. if (!ren.styledMode) {
  3734. obj.attr('fill', 'none');
  3735. }
  3736. // expando properties for use in animate and attr
  3737. extend(obj, {
  3738. symbolName: sym,
  3739. x: x,
  3740. y: y,
  3741. width: width,
  3742. height: height
  3743. });
  3744. if (options) {
  3745. extend(obj, options);
  3746. }
  3747. // Image symbols
  3748. } else if (isImage) {
  3749. imageSrc = symbol.match(imageRegex)[1];
  3750. // Create the image synchronously, add attribs async
  3751. obj = this.image(imageSrc);
  3752. // The image width is not always the same as the symbol width. The
  3753. // image may be centered within the symbol, as is the case when
  3754. // image shapes are used as label backgrounds, for example in flags.
  3755. obj.imgwidth = pick(
  3756. symbolSizes[imageSrc] && symbolSizes[imageSrc].width,
  3757. options && options.width
  3758. );
  3759. obj.imgheight = pick(
  3760. symbolSizes[imageSrc] && symbolSizes[imageSrc].height,
  3761. options && options.height
  3762. );
  3763. /**
  3764. * Set the size and position
  3765. */
  3766. centerImage = function () {
  3767. obj.attr({
  3768. width: obj.width,
  3769. height: obj.height
  3770. });
  3771. };
  3772. /**
  3773. * Width and height setters that take both the image's physical size
  3774. * and the label size into consideration, and translates the image
  3775. * to center within the label.
  3776. */
  3777. ['width', 'height'].forEach(function (key) {
  3778. obj[key + 'Setter'] = function (value, key) {
  3779. var attribs = {},
  3780. imgSize = this['img' + key],
  3781. trans = key === 'width' ? 'translateX' : 'translateY';
  3782. this[key] = value;
  3783. if (defined(imgSize)) {
  3784. if (this.element) {
  3785. this.element.setAttribute(key, imgSize);
  3786. }
  3787. if (!this.alignByTranslate) {
  3788. attribs[trans] = ((this[key] || 0) - imgSize) / 2;
  3789. this.attr(attribs);
  3790. }
  3791. }
  3792. };
  3793. });
  3794. if (defined(x)) {
  3795. obj.attr({
  3796. x: x,
  3797. y: y
  3798. });
  3799. }
  3800. obj.isImg = true;
  3801. if (defined(obj.imgwidth) && defined(obj.imgheight)) {
  3802. centerImage();
  3803. } else {
  3804. // Initialize image to be 0 size so export will still function
  3805. // if there's no cached sizes.
  3806. obj.attr({ width: 0, height: 0 });
  3807. // Create a dummy JavaScript image to get the width and height.
  3808. createElement('img', {
  3809. onload: function () {
  3810. var chart = charts[ren.chartIndex];
  3811. // Special case for SVGs on IE11, the width is not
  3812. // accessible until the image is part of the DOM
  3813. // (#2854).
  3814. if (this.width === 0) {
  3815. css(this, {
  3816. position: 'absolute',
  3817. top: '-999em'
  3818. });
  3819. doc.body.appendChild(this);
  3820. }
  3821. // Center the image
  3822. symbolSizes[imageSrc] = { // Cache for next
  3823. width: this.width,
  3824. height: this.height
  3825. };
  3826. obj.imgwidth = this.width;
  3827. obj.imgheight = this.height;
  3828. if (obj.element) {
  3829. centerImage();
  3830. }
  3831. // Clean up after #2854 workaround.
  3832. if (this.parentNode) {
  3833. this.parentNode.removeChild(this);
  3834. }
  3835. // Fire the load event when all external images are
  3836. // loaded
  3837. ren.imgCount--;
  3838. if (!ren.imgCount && chart && chart.onload) {
  3839. chart.onload();
  3840. }
  3841. },
  3842. src: imageSrc
  3843. });
  3844. this.imgCount++;
  3845. }
  3846. }
  3847. return obj;
  3848. },
  3849. /**
  3850. * An extendable collection of functions for defining symbol paths.
  3851. *
  3852. * @name Highcharts.SVGRenderer#symbols
  3853. * @type {Highcharts.SymbolDictionary}
  3854. */
  3855. symbols: {
  3856. 'circle': function (x, y, w, h) {
  3857. // Return a full arc
  3858. return this.arc(x + w / 2, y + h / 2, w / 2, h / 2, {
  3859. start: 0,
  3860. end: Math.PI * 2,
  3861. open: false
  3862. });
  3863. },
  3864. 'square': function (x, y, w, h) {
  3865. return [
  3866. 'M', x, y,
  3867. 'L', x + w, y,
  3868. x + w, y + h,
  3869. x, y + h,
  3870. 'Z'
  3871. ];
  3872. },
  3873. 'triangle': function (x, y, w, h) {
  3874. return [
  3875. 'M', x + w / 2, y,
  3876. 'L', x + w, y + h,
  3877. x, y + h,
  3878. 'Z'
  3879. ];
  3880. },
  3881. 'triangle-down': function (x, y, w, h) {
  3882. return [
  3883. 'M', x, y,
  3884. 'L', x + w, y,
  3885. x + w / 2, y + h,
  3886. 'Z'
  3887. ];
  3888. },
  3889. 'diamond': function (x, y, w, h) {
  3890. return [
  3891. 'M', x + w / 2, y,
  3892. 'L', x + w, y + h / 2,
  3893. x + w / 2, y + h,
  3894. x, y + h / 2,
  3895. 'Z'
  3896. ];
  3897. },
  3898. 'arc': function (x, y, w, h, options) {
  3899. var start = options.start,
  3900. rx = options.r || w,
  3901. ry = options.r || h || w,
  3902. proximity = 0.001,
  3903. fullCircle =
  3904. Math.abs(options.end - options.start - 2 * Math.PI) <
  3905. proximity,
  3906. // Substract a small number to prevent cos and sin of start and
  3907. // end from becoming equal on 360 arcs (related: #1561)
  3908. end = options.end - proximity,
  3909. innerRadius = options.innerR,
  3910. open = pick(options.open, fullCircle),
  3911. cosStart = Math.cos(start),
  3912. sinStart = Math.sin(start),
  3913. cosEnd = Math.cos(end),
  3914. sinEnd = Math.sin(end),
  3915. // Proximity takes care of rounding errors around PI (#6971)
  3916. longArc = options.end - start - Math.PI < proximity ? 0 : 1,
  3917. arc;
  3918. arc = [
  3919. 'M',
  3920. x + rx * cosStart,
  3921. y + ry * sinStart,
  3922. 'A', // arcTo
  3923. rx, // x radius
  3924. ry, // y radius
  3925. 0, // slanting
  3926. longArc, // long or short arc
  3927. 1, // clockwise
  3928. x + rx * cosEnd,
  3929. y + ry * sinEnd
  3930. ];
  3931. if (defined(innerRadius)) {
  3932. arc.push(
  3933. open ? 'M' : 'L',
  3934. x + innerRadius * cosEnd,
  3935. y + innerRadius * sinEnd,
  3936. 'A', // arcTo
  3937. innerRadius, // x radius
  3938. innerRadius, // y radius
  3939. 0, // slanting
  3940. longArc, // long or short arc
  3941. 0, // clockwise
  3942. x + innerRadius * cosStart,
  3943. y + innerRadius * sinStart
  3944. );
  3945. }
  3946. arc.push(open ? '' : 'Z'); // close
  3947. return arc;
  3948. },
  3949. /**
  3950. * Callout shape used for default tooltips, also used for rounded
  3951. * rectangles in VML
  3952. */
  3953. 'callout': function (x, y, w, h, options) {
  3954. var arrowLength = 6,
  3955. halfDistance = 6,
  3956. r = Math.min((options && options.r) || 0, w, h),
  3957. safeDistance = r + halfDistance,
  3958. anchorX = options && options.anchorX,
  3959. anchorY = options && options.anchorY,
  3960. path;
  3961. path = [
  3962. 'M', x + r, y,
  3963. 'L', x + w - r, y, // top side
  3964. 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
  3965. 'L', x + w, y + h - r, // right side
  3966. 'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-rgt
  3967. 'L', x + r, y + h, // bottom side
  3968. 'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
  3969. 'L', x, y + r, // left side
  3970. 'C', x, y, x, y, x + r, y // top-left corner
  3971. ];
  3972. // Anchor on right side
  3973. if (anchorX && anchorX > w) {
  3974. // Chevron
  3975. if (
  3976. anchorY > y + safeDistance &&
  3977. anchorY < y + h - safeDistance
  3978. ) {
  3979. path.splice(
  3980. 13,
  3981. 3,
  3982. 'L', x + w, anchorY - halfDistance,
  3983. x + w + arrowLength, anchorY,
  3984. x + w, anchorY + halfDistance,
  3985. x + w, y + h - r
  3986. );
  3987. // Simple connector
  3988. } else {
  3989. path.splice(
  3990. 13,
  3991. 3,
  3992. 'L', x + w, h / 2,
  3993. anchorX, anchorY,
  3994. x + w, h / 2,
  3995. x + w, y + h - r
  3996. );
  3997. }
  3998. // Anchor on left side
  3999. } else if (anchorX && anchorX < 0) {
  4000. // Chevron
  4001. if (
  4002. anchorY > y + safeDistance &&
  4003. anchorY < y + h - safeDistance
  4004. ) {
  4005. path.splice(
  4006. 33,
  4007. 3,
  4008. 'L', x, anchorY + halfDistance,
  4009. x - arrowLength, anchorY,
  4010. x, anchorY - halfDistance,
  4011. x, y + r
  4012. );
  4013. // Simple connector
  4014. } else {
  4015. path.splice(
  4016. 33,
  4017. 3,
  4018. 'L', x, h / 2,
  4019. anchorX, anchorY,
  4020. x, h / 2,
  4021. x, y + r
  4022. );
  4023. }
  4024. } else if ( // replace bottom
  4025. anchorY &&
  4026. anchorY > h &&
  4027. anchorX > x + safeDistance &&
  4028. anchorX < x + w - safeDistance
  4029. ) {
  4030. path.splice(
  4031. 23,
  4032. 3,
  4033. 'L', anchorX + halfDistance, y + h,
  4034. anchorX, y + h + arrowLength,
  4035. anchorX - halfDistance, y + h,
  4036. x + r, y + h
  4037. );
  4038. } else if ( // replace top
  4039. anchorY &&
  4040. anchorY < 0 &&
  4041. anchorX > x + safeDistance &&
  4042. anchorX < x + w - safeDistance
  4043. ) {
  4044. path.splice(
  4045. 3,
  4046. 3,
  4047. 'L', anchorX - halfDistance, y,
  4048. anchorX, y - arrowLength,
  4049. anchorX + halfDistance, y,
  4050. w - r, y
  4051. );
  4052. }
  4053. return path;
  4054. }
  4055. },
  4056. /**
  4057. * Define a clipping rectangle. The clipping rectangle is later applied
  4058. * to {@link SVGElement} objects through the {@link SVGElement#clip}
  4059. * function.
  4060. *
  4061. * @example
  4062. * var circle = renderer.circle(100, 100, 100)
  4063. * .attr({ fill: 'red' })
  4064. * .add();
  4065. * var clipRect = renderer.clipRect(100, 100, 100, 100);
  4066. *
  4067. * // Leave only the lower right quarter visible
  4068. * circle.clip(clipRect);
  4069. *
  4070. * @function Highcharts.SVGRenderer#clipRect
  4071. *
  4072. * @param {string} id
  4073. *
  4074. * @param {number} x
  4075. *
  4076. * @param {number} y
  4077. *
  4078. * @param {number} width
  4079. *
  4080. * @param {number} height
  4081. *
  4082. * @return {Highcharts.ClipRectElement}
  4083. * A clipping rectangle.
  4084. */
  4085. clipRect: function (x, y, width, height) {
  4086. var wrapper,
  4087. id = H.uniqueKey(),
  4088. clipPath = this.createElement('clipPath').attr({
  4089. id: id
  4090. }).add(this.defs);
  4091. wrapper = this.rect(x, y, width, height, 0).add(clipPath);
  4092. wrapper.id = id;
  4093. wrapper.clipPath = clipPath;
  4094. wrapper.count = 0;
  4095. return wrapper;
  4096. },
  4097. /**
  4098. * Draw text. The text can contain a subset of HTML, like spans and anchors
  4099. * and some basic text styling of these. For more advanced features like
  4100. * border and background, use {@link Highcharts.SVGRenderer#label} instead.
  4101. * To update the text after render, run `text.attr({ text: 'New text' })`.
  4102. *
  4103. * @sample highcharts/members/renderer-text-on-chart/
  4104. * Annotate the chart freely
  4105. * @sample highcharts/members/renderer-on-chart/
  4106. * Annotate with a border and in response to the data
  4107. * @sample highcharts/members/renderer-text/
  4108. * Formatted text
  4109. *
  4110. * @function Highcharts.SVGRenderer#text
  4111. *
  4112. * @param {string} str
  4113. * The text of (subset) HTML to draw.
  4114. *
  4115. * @param {number} x
  4116. * The x position of the text's lower left corner.
  4117. *
  4118. * @param {number} y
  4119. * The y position of the text's lower left corner.
  4120. *
  4121. * @param {boolean} [useHTML=false]
  4122. * Use HTML to render the text.
  4123. *
  4124. * @return {Highcharts.SVGElement}
  4125. * The text object.
  4126. */
  4127. text: function (str, x, y, useHTML) {
  4128. // declare variables
  4129. var renderer = this,
  4130. wrapper,
  4131. attribs = {};
  4132. if (useHTML && (renderer.allowHTML || !renderer.forExport)) {
  4133. return renderer.html(str, x, y);
  4134. }
  4135. attribs.x = Math.round(x || 0); // X always needed for line-wrap logic
  4136. if (y) {
  4137. attribs.y = Math.round(y);
  4138. }
  4139. if (defined(str)) {
  4140. attribs.text = str;
  4141. }
  4142. wrapper = renderer.createElement('text')
  4143. .attr(attribs);
  4144. if (!useHTML) {
  4145. wrapper.xSetter = function (value, key, element) {
  4146. var tspans = element.getElementsByTagName('tspan'),
  4147. tspan,
  4148. parentVal = element.getAttribute(key),
  4149. i;
  4150. for (i = 0; i < tspans.length; i++) {
  4151. tspan = tspans[i];
  4152. // If the x values are equal, the tspan represents a
  4153. // linebreak
  4154. if (tspan.getAttribute(key) === parentVal) {
  4155. tspan.setAttribute(key, value);
  4156. }
  4157. }
  4158. element.setAttribute(key, value);
  4159. };
  4160. }
  4161. return wrapper;
  4162. },
  4163. /**
  4164. * Utility to return the baseline offset and total line height from the font
  4165. * size.
  4166. *
  4167. * @function Highcharts.SVGRenderer#fontMetrics
  4168. *
  4169. * @param {string} [fontSize]
  4170. * The current font size to inspect. If not given, the font size
  4171. * will be found from the DOM element.
  4172. *
  4173. * @param {Highcharts.SVGElement|Highcharts.SVGDOMElement} [elem]
  4174. * The element to inspect for a current font size.
  4175. *
  4176. * @return {Highcharts.FontMetricsObject}
  4177. * The font metrics.
  4178. */
  4179. fontMetrics: function (fontSize, elem) {
  4180. var lineHeight,
  4181. baseline;
  4182. if (
  4183. (this.styledMode || !/px/.test(fontSize)) &&
  4184. win.getComputedStyle // old IE doesn't support it
  4185. ) {
  4186. fontSize = elem && SVGElement.prototype.getStyle.call(
  4187. elem,
  4188. 'font-size'
  4189. );
  4190. } else {
  4191. fontSize = fontSize ||
  4192. // When the elem is a DOM element (#5932)
  4193. (elem && elem.style && elem.style.fontSize) ||
  4194. // Fall back on the renderer style default
  4195. (this.style && this.style.fontSize);
  4196. }
  4197. // Handle different units
  4198. if (/px/.test(fontSize)) {
  4199. fontSize = pInt(fontSize);
  4200. } else {
  4201. fontSize = 12;
  4202. }
  4203. // Empirical values found by comparing font size and bounding box
  4204. // height. Applies to the default font family.
  4205. // https://jsfiddle.net/highcharts/7xvn7/
  4206. lineHeight = fontSize < 24 ? fontSize + 3 : Math.round(fontSize * 1.2);
  4207. baseline = Math.round(lineHeight * 0.8);
  4208. return {
  4209. h: lineHeight,
  4210. b: baseline,
  4211. f: fontSize
  4212. };
  4213. },
  4214. /**
  4215. * Correct X and Y positioning of a label for rotation (#1764).
  4216. *
  4217. * @private
  4218. * @function Highcharts.SVGRenderer#rotCorr
  4219. *
  4220. * @param {number} baseline
  4221. *
  4222. * @param {number} rotation
  4223. *
  4224. * @param {boolean} alterY
  4225. */
  4226. rotCorr: function (baseline, rotation, alterY) {
  4227. var y = baseline;
  4228. if (rotation && alterY) {
  4229. y = Math.max(y * Math.cos(rotation * deg2rad), 4);
  4230. }
  4231. return {
  4232. x: (-baseline / 3) * Math.sin(rotation * deg2rad),
  4233. y: y
  4234. };
  4235. },
  4236. /**
  4237. * Draw a label, which is an extended text element with support for border
  4238. * and background. Highcharts creates a `g` element with a text and a `path`
  4239. * or `rect` inside, to make it behave somewhat like a HTML div. Border and
  4240. * background are set through `stroke`, `stroke-width` and `fill` attributes
  4241. * using the {@link Highcharts.SVGElement#attr|attr} method. To update the
  4242. * text after render, run `label.attr({ text: 'New text' })`.
  4243. *
  4244. * @sample highcharts/members/renderer-label-on-chart/
  4245. * A label on the chart
  4246. *
  4247. * @function Highcharts.SVGRenderer#label
  4248. *
  4249. * @param {string} str
  4250. * The initial text string or (subset) HTML to render.
  4251. *
  4252. * @param {number} x
  4253. * The x position of the label's left side.
  4254. *
  4255. * @param {number} y
  4256. * The y position of the label's top side or baseline, depending on
  4257. * the `baseline` parameter.
  4258. *
  4259. * @param {string} [shape='rect']
  4260. * The shape of the label's border/background, if any. Defaults to
  4261. * `rect`. Other possible values are `callout` or other shapes
  4262. * defined in {@link Highcharts.SVGRenderer#symbols}.
  4263. *
  4264. * @param {string} [shape='rect']
  4265. * The shape of the label's border/background, if any. Defaults to
  4266. * `rect`. Other possible values are `callout` or other shapes
  4267. * defined in {@link Highcharts.SVGRenderer#symbols}.
  4268. *
  4269. * @param {number} [anchorX]
  4270. * In case the `shape` has a pointer, like a flag, this is the
  4271. * coordinates it should be pinned to.
  4272. *
  4273. * @param {number} [anchorY]
  4274. * In case the `shape` has a pointer, like a flag, this is the
  4275. * coordinates it should be pinned to.
  4276. *
  4277. * @param {boolean} [useHTML=false]
  4278. * Wether to use HTML to render the label.
  4279. *
  4280. * @param {boolean} [baseline=false]
  4281. * Whether to position the label relative to the text baseline,
  4282. * like {@link Highcharts.SVGRenderer#text|renderer.text}, or to the
  4283. * upper border of the rectangle.
  4284. *
  4285. * @param {string} [className]
  4286. * Class name for the group.
  4287. *
  4288. * @return {Highcharts.SVGElement}
  4289. * The generated label.
  4290. */
  4291. label: function (
  4292. str,
  4293. x,
  4294. y,
  4295. shape,
  4296. anchorX,
  4297. anchorY,
  4298. useHTML,
  4299. baseline,
  4300. className
  4301. ) {
  4302. var renderer = this,
  4303. styledMode = renderer.styledMode,
  4304. wrapper = renderer.g(className !== 'button' && 'label'),
  4305. text = wrapper.text = renderer.text('', 0, 0, useHTML)
  4306. .attr({
  4307. zIndex: 1
  4308. }),
  4309. box,
  4310. bBox,
  4311. alignFactor = 0,
  4312. padding = 3,
  4313. paddingLeft = 0,
  4314. width,
  4315. height,
  4316. wrapperX,
  4317. wrapperY,
  4318. textAlign,
  4319. deferredAttr = {},
  4320. strokeWidth,
  4321. baselineOffset,
  4322. hasBGImage = /^url\((.*?)\)$/.test(shape),
  4323. needsBox = styledMode || hasBGImage,
  4324. getCrispAdjust = function () {
  4325. return styledMode ?
  4326. box.strokeWidth() % 2 / 2 :
  4327. (strokeWidth ? parseInt(strokeWidth, 10) : 0) % 2 / 2;
  4328. },
  4329. updateBoxSize,
  4330. updateTextPadding,
  4331. boxAttr;
  4332. if (className) {
  4333. wrapper.addClass('highcharts-' + className);
  4334. }
  4335. /* This function runs after the label is added to the DOM (when the
  4336. bounding box is available), and after the text of the label is
  4337. updated to detect the new bounding box and reflect it in the border
  4338. box. */
  4339. updateBoxSize = function () {
  4340. var style = text.element.style,
  4341. crispAdjust,
  4342. attribs = {};
  4343. bBox = (
  4344. (width === undefined || height === undefined || textAlign) &&
  4345. defined(text.textStr) &&
  4346. text.getBBox()
  4347. ); // #3295 && 3514 box failure when string equals 0
  4348. wrapper.width = (
  4349. (width || bBox.width || 0) +
  4350. 2 * padding +
  4351. paddingLeft
  4352. );
  4353. wrapper.height = (height || bBox.height || 0) + 2 * padding;
  4354. // Update the label-scoped y offset
  4355. baselineOffset = padding + Math.min(
  4356. renderer.fontMetrics(style && style.fontSize, text).b,
  4357. // Math.min because of inline style (#9400)
  4358. bBox ? bBox.height : Infinity
  4359. );
  4360. if (needsBox) {
  4361. // Create the border box if it is not already present
  4362. if (!box) {
  4363. // Symbol definition exists (#5324)
  4364. wrapper.box = box = renderer.symbols[shape] || hasBGImage ?
  4365. renderer.symbol(shape) :
  4366. renderer.rect();
  4367. box.addClass( // Don't use label className for buttons
  4368. (className === 'button' ? '' : 'highcharts-label-box') +
  4369. (className ? ' highcharts-' + className + '-box' : '')
  4370. );
  4371. box.add(wrapper);
  4372. crispAdjust = getCrispAdjust();
  4373. attribs.x = crispAdjust;
  4374. attribs.y = (baseline ? -baselineOffset : 0) + crispAdjust;
  4375. }
  4376. // Apply the box attributes
  4377. attribs.width = Math.round(wrapper.width);
  4378. attribs.height = Math.round(wrapper.height);
  4379. box.attr(extend(attribs, deferredAttr));
  4380. deferredAttr = {};
  4381. }
  4382. };
  4383. /*
  4384. * This function runs after setting text or padding, but only if padding
  4385. * is changed.
  4386. */
  4387. updateTextPadding = function () {
  4388. var textX = paddingLeft + padding,
  4389. textY;
  4390. // determin y based on the baseline
  4391. textY = baseline ? 0 : baselineOffset;
  4392. // compensate for alignment
  4393. if (
  4394. defined(width) &&
  4395. bBox &&
  4396. (textAlign === 'center' || textAlign === 'right')
  4397. ) {
  4398. textX += { center: 0.5, right: 1 }[textAlign] *
  4399. (width - bBox.width);
  4400. }
  4401. // update if anything changed
  4402. if (textX !== text.x || textY !== text.y) {
  4403. text.attr('x', textX);
  4404. // #8159 - prevent misplaced data labels in treemap
  4405. // (useHTML: true)
  4406. if (text.hasBoxWidthChanged) {
  4407. bBox = text.getBBox(true);
  4408. updateBoxSize();
  4409. }
  4410. if (textY !== undefined) {
  4411. text.attr('y', textY);
  4412. }
  4413. }
  4414. // record current values
  4415. text.x = textX;
  4416. text.y = textY;
  4417. };
  4418. /*
  4419. * Set a box attribute, or defer it if the box is not yet created
  4420. */
  4421. boxAttr = function (key, value) {
  4422. if (box) {
  4423. box.attr(key, value);
  4424. } else {
  4425. deferredAttr[key] = value;
  4426. }
  4427. };
  4428. /*
  4429. * After the text element is added, get the desired size of the border
  4430. * box and add it before the text in the DOM.
  4431. */
  4432. wrapper.onAdd = function () {
  4433. text.add(wrapper);
  4434. wrapper.attr({
  4435. // Alignment is available now (#3295, 0 not rendered if given
  4436. // as a value)
  4437. text: (str || str === 0) ? str : '',
  4438. x: x,
  4439. y: y
  4440. });
  4441. if (box && defined(anchorX)) {
  4442. wrapper.attr({
  4443. anchorX: anchorX,
  4444. anchorY: anchorY
  4445. });
  4446. }
  4447. };
  4448. /*
  4449. * Add specific attribute setters.
  4450. */
  4451. // only change local variables
  4452. wrapper.widthSetter = function (value) {
  4453. width = H.isNumber(value) ? value : null; // width:auto => null
  4454. };
  4455. wrapper.heightSetter = function (value) {
  4456. height = value;
  4457. };
  4458. wrapper['text-alignSetter'] = function (value) {
  4459. textAlign = value;
  4460. };
  4461. wrapper.paddingSetter = function (value) {
  4462. if (defined(value) && value !== padding) {
  4463. padding = wrapper.padding = value;
  4464. updateTextPadding();
  4465. }
  4466. };
  4467. wrapper.paddingLeftSetter = function (value) {
  4468. if (defined(value) && value !== paddingLeft) {
  4469. paddingLeft = value;
  4470. updateTextPadding();
  4471. }
  4472. };
  4473. // change local variable and prevent setting attribute on the group
  4474. wrapper.alignSetter = function (value) {
  4475. value = { left: 0, center: 0.5, right: 1 }[value];
  4476. if (value !== alignFactor) {
  4477. alignFactor = value;
  4478. // Bounding box exists, means we're dynamically changing
  4479. if (bBox) {
  4480. wrapper.attr({ x: wrapperX }); // #5134
  4481. }
  4482. }
  4483. };
  4484. // apply these to the box and the text alike
  4485. wrapper.textSetter = function (value) {
  4486. if (value !== undefined) {
  4487. text.textSetter(value);
  4488. }
  4489. updateBoxSize();
  4490. updateTextPadding();
  4491. };
  4492. // apply these to the box but not to the text
  4493. wrapper['stroke-widthSetter'] = function (value, key) {
  4494. if (value) {
  4495. needsBox = true;
  4496. }
  4497. strokeWidth = this['stroke-width'] = value;
  4498. boxAttr(key, value);
  4499. };
  4500. if (styledMode) {
  4501. wrapper.rSetter = function (value, key) {
  4502. boxAttr(key, value);
  4503. };
  4504. } else {
  4505. wrapper.strokeSetter =
  4506. wrapper.fillSetter =
  4507. wrapper.rSetter = function (value, key) {
  4508. if (key !== 'r') {
  4509. if (key === 'fill' && value) {
  4510. needsBox = true;
  4511. }
  4512. // for animation getter (#6776)
  4513. wrapper[key] = value;
  4514. }
  4515. boxAttr(key, value);
  4516. };
  4517. }
  4518. wrapper.anchorXSetter = function (value, key) {
  4519. anchorX = wrapper.anchorX = value;
  4520. boxAttr(key, Math.round(value) - getCrispAdjust() - wrapperX);
  4521. };
  4522. wrapper.anchorYSetter = function (value, key) {
  4523. anchorY = wrapper.anchorY = value;
  4524. boxAttr(key, value - wrapperY);
  4525. };
  4526. // rename attributes
  4527. wrapper.xSetter = function (value) {
  4528. wrapper.x = value; // for animation getter
  4529. if (alignFactor) {
  4530. value -= alignFactor * ((width || bBox.width) + 2 * padding);
  4531. // Force animation even when setting to the same value (#7898)
  4532. wrapper['forceAnimate:x'] = true;
  4533. }
  4534. wrapperX = Math.round(value);
  4535. wrapper.attr('translateX', wrapperX);
  4536. };
  4537. wrapper.ySetter = function (value) {
  4538. wrapperY = wrapper.y = Math.round(value);
  4539. wrapper.attr('translateY', wrapperY);
  4540. };
  4541. // Redirect certain methods to either the box or the text
  4542. var baseCss = wrapper.css;
  4543. var wrapperExtension = {
  4544. /**
  4545. * Pick up some properties and apply them to the text instead of the
  4546. * wrapper.
  4547. */
  4548. css: function (styles) {
  4549. if (styles) {
  4550. var textStyles = {};
  4551. // Create a copy to avoid altering the original object
  4552. // (#537)
  4553. styles = merge(styles);
  4554. wrapper.textProps.forEach(function (prop) {
  4555. if (styles[prop] !== undefined) {
  4556. textStyles[prop] = styles[prop];
  4557. delete styles[prop];
  4558. }
  4559. });
  4560. text.css(textStyles);
  4561. // Update existing text and box
  4562. if ('width' in textStyles) {
  4563. updateBoxSize();
  4564. }
  4565. // Keep updated (#9400)
  4566. if ('fontSize' in textStyles) {
  4567. updateBoxSize();
  4568. updateTextPadding();
  4569. }
  4570. }
  4571. return baseCss.call(wrapper, styles);
  4572. },
  4573. /*
  4574. * Return the bounding box of the box, not the group.
  4575. */
  4576. getBBox: function () {
  4577. return {
  4578. width: bBox.width + 2 * padding,
  4579. height: bBox.height + 2 * padding,
  4580. x: bBox.x - padding,
  4581. y: bBox.y - padding
  4582. };
  4583. },
  4584. /**
  4585. * Destroy and release memory.
  4586. */
  4587. destroy: function () {
  4588. // Added by button implementation
  4589. removeEvent(wrapper.element, 'mouseenter');
  4590. removeEvent(wrapper.element, 'mouseleave');
  4591. if (text) {
  4592. text = text.destroy();
  4593. }
  4594. if (box) {
  4595. box = box.destroy();
  4596. }
  4597. // Call base implementation to destroy the rest
  4598. SVGElement.prototype.destroy.call(wrapper);
  4599. // Release local pointers (#1298)
  4600. wrapper =
  4601. renderer =
  4602. updateBoxSize =
  4603. updateTextPadding =
  4604. boxAttr = null;
  4605. }
  4606. };
  4607. if (!styledMode) {
  4608. /**
  4609. * Apply the shadow to the box.
  4610. *
  4611. * @ignore
  4612. * @function Highcharts.SVGElement#shadow
  4613. *
  4614. * @return {Highcharts.SVGElement}
  4615. */
  4616. wrapperExtension.shadow = function (b) {
  4617. if (b) {
  4618. updateBoxSize();
  4619. if (box) {
  4620. box.shadow(b);
  4621. }
  4622. }
  4623. return wrapper;
  4624. };
  4625. }
  4626. return extend(wrapper, wrapperExtension);
  4627. }
  4628. }); // end SVGRenderer
  4629. // general renderer
  4630. H.Renderer = SVGRenderer;