SvgRenderer.js 164 KB

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