SVGRenderer.js 86 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329
  1. /* *
  2. *
  3. * (c) 2010-2020 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 Color from './Color.js';
  12. import H from './Globals.js';
  13. import SVGElement from './SVGElement.js';
  14. import SVGLabel from './SVGLabel.js';
  15. import U from './Utilities.js';
  16. var addEvent = U.addEvent, attr = U.attr, createElement = U.createElement, css = U.css, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, erase = U.erase, extend = U.extend, isArray = U.isArray, isNumber = U.isNumber, isObject = U.isObject, isString = U.isString, merge = U.merge, objectEach = U.objectEach, pick = U.pick, pInt = U.pInt, removeEvent = U.removeEvent, splat = U.splat, stop = U.stop, uniqueKey = U.uniqueKey;
  17. /**
  18. * A clipping rectangle that can be applied to one or more {@link SVGElement}
  19. * instances. It is instanciated with the {@link SVGRenderer#clipRect} function
  20. * and applied with the {@link SVGElement#clip} function.
  21. *
  22. * @example
  23. * var circle = renderer.circle(100, 100, 100)
  24. * .attr({ fill: 'red' })
  25. * .add();
  26. * var clipRect = renderer.clipRect(100, 100, 100, 100);
  27. *
  28. * // Leave only the lower right quarter visible
  29. * circle.clip(clipRect);
  30. *
  31. * @typedef {Highcharts.SVGElement} Highcharts.ClipRectElement
  32. */
  33. /**
  34. * The font metrics.
  35. *
  36. * @interface Highcharts.FontMetricsObject
  37. */ /**
  38. * The baseline relative to the top of the box.
  39. *
  40. * @name Highcharts.FontMetricsObject#b
  41. * @type {number}
  42. */ /**
  43. * The font size.
  44. *
  45. * @name Highcharts.FontMetricsObject#f
  46. * @type {number}
  47. */ /**
  48. * The line height.
  49. *
  50. * @name Highcharts.FontMetricsObject#h
  51. * @type {number}
  52. */
  53. /**
  54. * An object containing `x` and `y` properties for the position of an element.
  55. *
  56. * @interface Highcharts.PositionObject
  57. */ /**
  58. * X position of the element.
  59. * @name Highcharts.PositionObject#x
  60. * @type {number}
  61. */ /**
  62. * Y position of the element.
  63. * @name Highcharts.PositionObject#y
  64. * @type {number}
  65. */
  66. /**
  67. * A rectangle.
  68. *
  69. * @interface Highcharts.RectangleObject
  70. */ /**
  71. * Height of the rectangle.
  72. * @name Highcharts.RectangleObject#height
  73. * @type {number}
  74. */ /**
  75. * Width of the rectangle.
  76. * @name Highcharts.RectangleObject#width
  77. * @type {number}
  78. */ /**
  79. * Horizontal position of the rectangle.
  80. * @name Highcharts.RectangleObject#x
  81. * @type {number}
  82. */ /**
  83. * Vertical position of the rectangle.
  84. * @name Highcharts.RectangleObject#y
  85. * @type {number}
  86. */
  87. /**
  88. * The shadow options.
  89. *
  90. * @interface Highcharts.ShadowOptionsObject
  91. */ /**
  92. * The shadow color.
  93. * @name Highcharts.ShadowOptionsObject#color
  94. * @type {Highcharts.ColorString|undefined}
  95. * @default #000000
  96. */ /**
  97. * The horizontal offset from the element.
  98. *
  99. * @name Highcharts.ShadowOptionsObject#offsetX
  100. * @type {number|undefined}
  101. * @default 1
  102. */ /**
  103. * The vertical offset from the element.
  104. * @name Highcharts.ShadowOptionsObject#offsetY
  105. * @type {number|undefined}
  106. * @default 1
  107. */ /**
  108. * The shadow opacity.
  109. *
  110. * @name Highcharts.ShadowOptionsObject#opacity
  111. * @type {number|undefined}
  112. * @default 0.15
  113. */ /**
  114. * The shadow width or distance from the element.
  115. * @name Highcharts.ShadowOptionsObject#width
  116. * @type {number|undefined}
  117. * @default 3
  118. */
  119. /**
  120. * @interface Highcharts.SizeObject
  121. */ /**
  122. * @name Highcharts.SizeObject#height
  123. * @type {number}
  124. */ /**
  125. * @name Highcharts.SizeObject#width
  126. * @type {number}
  127. */
  128. /**
  129. * Serialized form of an SVG definition, including children. Some key
  130. * property names are reserved: tagName, textContent, and children.
  131. *
  132. * @interface Highcharts.SVGDefinitionObject
  133. */ /**
  134. * @name Highcharts.SVGDefinitionObject#[key:string]
  135. * @type {boolean|number|string|Array<Highcharts.SVGDefinitionObject>|undefined}
  136. */ /**
  137. * @name Highcharts.SVGDefinitionObject#children
  138. * @type {Array<Highcharts.SVGDefinitionObject>|undefined}
  139. */ /**
  140. * @name Highcharts.SVGDefinitionObject#tagName
  141. * @type {string|undefined}
  142. */ /**
  143. * @name Highcharts.SVGDefinitionObject#textContent
  144. * @type {string|undefined}
  145. */
  146. /**
  147. * Array of path commands, that will go into the `d` attribute of an SVG
  148. * element.
  149. *
  150. * @typedef {Array<Array<Highcharts.SVGPathCommand,number?,number?,number?,number?,number?,number?,number?>>} Highcharts.SVGPathArray
  151. */
  152. /**
  153. * Possible path commands in an SVG path array. Valid values are `A`, `C`, `H`,
  154. * `L`, `M`, `Q`, `S`, `T`, `V`, `Z`.
  155. *
  156. * @typedef {string} Highcharts.SVGPathCommand
  157. * @validvalue ["a","c","h","l","m","q","s","t","v","z","A","C","H","L","M","Q","S","T","V","Z"]
  158. */
  159. /**
  160. * An extendable collection of functions for defining symbol paths. Symbols are
  161. * used internally for point markers, button and label borders and backgrounds,
  162. * or custom shapes. Extendable by adding to {@link SVGRenderer#symbols}.
  163. *
  164. * @interface Highcharts.SymbolDictionary
  165. */ /**
  166. * @name Highcharts.SymbolDictionary#[key:string]
  167. * @type {Function|undefined}
  168. */ /**
  169. * @name Highcharts.SymbolDictionary#arc
  170. * @type {Function|undefined}
  171. */ /**
  172. * @name Highcharts.SymbolDictionary#callout
  173. * @type {Function|undefined}
  174. */ /**
  175. * @name Highcharts.SymbolDictionary#circle
  176. * @type {Function|undefined}
  177. */ /**
  178. * @name Highcharts.SymbolDictionary#diamond
  179. * @type {Function|undefined}
  180. */ /**
  181. * @name Highcharts.SymbolDictionary#square
  182. * @type {Function|undefined}
  183. */ /**
  184. * @name Highcharts.SymbolDictionary#triangle
  185. * @type {Function|undefined}
  186. */
  187. /**
  188. * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`, `triangle`,
  189. * and `triangle-down`. Symbols are used internally for point markers, button
  190. * and label borders and backgrounds, or custom shapes. Extendable by adding to
  191. * {@link SVGRenderer#symbols}.
  192. *
  193. * @typedef {"arc"|"callout"|"circle"|"diamond"|"square"|"triangle"|"triangle-down"} Highcharts.SymbolKeyValue
  194. */
  195. /**
  196. * Additional options, depending on the actual symbol drawn.
  197. *
  198. * @interface Highcharts.SymbolOptionsObject
  199. */ /**
  200. * The anchor X position for the `callout` symbol. This is where the chevron
  201. * points to.
  202. *
  203. * @name Highcharts.SymbolOptionsObject#anchorX
  204. * @type {number|undefined}
  205. */ /**
  206. * The anchor Y position for the `callout` symbol. This is where the chevron
  207. * points to.
  208. *
  209. * @name Highcharts.SymbolOptionsObject#anchorY
  210. * @type {number|undefined}
  211. */ /**
  212. * The end angle of an `arc` symbol.
  213. *
  214. * @name Highcharts.SymbolOptionsObject#end
  215. * @type {number|undefined}
  216. */ /**
  217. * Whether to draw `arc` symbol open or closed.
  218. *
  219. * @name Highcharts.SymbolOptionsObject#open
  220. * @type {boolean|undefined}
  221. */ /**
  222. * The radius of an `arc` symbol, or the border radius for the `callout` symbol.
  223. *
  224. * @name Highcharts.SymbolOptionsObject#r
  225. * @type {number|undefined}
  226. */ /**
  227. * The start angle of an `arc` symbol.
  228. *
  229. * @name Highcharts.SymbolOptionsObject#start
  230. * @type {number|undefined}
  231. */
  232. /* eslint-disable no-invalid-this, valid-jsdoc */
  233. var charts = H.charts, deg2rad = H.deg2rad, doc = H.doc, isFirefox = H.isFirefox, isMS = H.isMS, isWebKit = H.isWebKit, noop = H.noop, svg = H.svg, SVG_NS = H.SVG_NS, symbolSizes = H.symbolSizes, win = H.win;
  234. /**
  235. * Allows direct access to the Highcharts rendering layer in order to draw
  236. * primitive shapes like circles, rectangles, paths or text directly on a chart,
  237. * or independent from any chart. The SVGRenderer represents a wrapper object
  238. * for SVG in modern browsers. Through the VMLRenderer, part of the `oldie.js`
  239. * module, it also brings vector graphics to IE <= 8.
  240. *
  241. * An existing chart's renderer can be accessed through {@link Chart.renderer}.
  242. * The renderer can also be used completely decoupled from a chart.
  243. *
  244. * @sample highcharts/members/renderer-on-chart
  245. * Annotating a chart programmatically.
  246. * @sample highcharts/members/renderer-basic
  247. * Independent SVG drawing.
  248. *
  249. * @example
  250. * // Use directly without a chart object.
  251. * var renderer = new Highcharts.Renderer(parentNode, 600, 400);
  252. *
  253. * @class
  254. * @name Highcharts.SVGRenderer
  255. *
  256. * @param {Highcharts.HTMLDOMElement} container
  257. * Where to put the SVG in the web page.
  258. *
  259. * @param {number} width
  260. * The width of the SVG.
  261. *
  262. * @param {number} height
  263. * The height of the SVG.
  264. *
  265. * @param {Highcharts.CSSObject} [style]
  266. * The box style, if not in styleMode
  267. *
  268. * @param {boolean} [forExport=false]
  269. * Whether the rendered content is intended for export.
  270. *
  271. * @param {boolean} [allowHTML=true]
  272. * Whether the renderer is allowed to include HTML text, which will be
  273. * projected on top of the SVG.
  274. *
  275. * @param {boolean} [styledMode=false]
  276. * Whether the renderer belongs to a chart that is in styled mode.
  277. * If it does, it will avoid setting presentational attributes in
  278. * some cases, but not when set explicitly through `.attr` and `.css`
  279. * etc.
  280. */
  281. var SVGRenderer = /** @class */ (function () {
  282. /* *
  283. *
  284. * Constructors
  285. *
  286. * */
  287. function SVGRenderer(container, width, height, style, forExport, allowHTML, styledMode) {
  288. /* *
  289. *
  290. * Properties
  291. *
  292. * */
  293. this.alignedObjects = void 0;
  294. /**
  295. * The root `svg` node of the renderer.
  296. *
  297. * @name Highcharts.SVGRenderer#box
  298. * @type {Highcharts.SVGDOMElement}
  299. */
  300. this.box = void 0;
  301. /**
  302. * The wrapper for the root `svg` node of the renderer.
  303. *
  304. * @name Highcharts.SVGRenderer#boxWrapper
  305. * @type {Highcharts.SVGElement}
  306. */
  307. this.boxWrapper = void 0;
  308. this.cache = void 0;
  309. this.cacheKeys = void 0;
  310. this.chartIndex = void 0;
  311. /**
  312. * A pointer to the `defs` node of the root SVG.
  313. *
  314. * @name Highcharts.SVGRenderer#defs
  315. * @type {Highcharts.SVGElement}
  316. */
  317. this.defs = void 0;
  318. this.globalAnimation = void 0;
  319. this.gradients = void 0;
  320. this.height = void 0;
  321. this.imgCount = void 0;
  322. this.isSVG = void 0;
  323. this.style = void 0;
  324. /**
  325. * Page url used for internal references.
  326. *
  327. * @private
  328. * @name Highcharts.SVGRenderer#url
  329. * @type {string}
  330. */
  331. this.url = void 0;
  332. this.width = void 0;
  333. this.init(container, width, height, style, forExport, allowHTML, styledMode);
  334. }
  335. /* *
  336. *
  337. * Functions
  338. *
  339. * */
  340. /**
  341. * Initialize the SVGRenderer. Overridable initializer function that takes
  342. * the same parameters as the constructor.
  343. *
  344. * @function Highcharts.SVGRenderer#init
  345. *
  346. * @param {Highcharts.HTMLDOMElement} container
  347. * Where to put the SVG in the web page.
  348. *
  349. * @param {number} width
  350. * The width of the SVG.
  351. *
  352. * @param {number} height
  353. * The height of the SVG.
  354. *
  355. * @param {Highcharts.CSSObject} [style]
  356. * The box style, if not in styleMode
  357. *
  358. * @param {boolean} [forExport=false]
  359. * Whether the rendered content is intended for export.
  360. *
  361. * @param {boolean} [allowHTML=true]
  362. * Whether the renderer is allowed to include HTML text, which will be
  363. * projected on top of the SVG.
  364. *
  365. * @param {boolean} [styledMode=false]
  366. * Whether the renderer belongs to a chart that is in styled mode. If it
  367. * does, it will avoid setting presentational attributes in some cases, but
  368. * not when set explicitly through `.attr` and `.css` etc.
  369. */
  370. SVGRenderer.prototype.init = function (container, width, height, style, forExport, allowHTML, styledMode) {
  371. var renderer = this, boxWrapper, element, desc;
  372. boxWrapper = renderer.createElement('svg')
  373. .attr({
  374. version: '1.1',
  375. 'class': 'highcharts-root'
  376. });
  377. if (!styledMode) {
  378. boxWrapper.css(this.getStyle(style));
  379. }
  380. element = boxWrapper.element;
  381. container.appendChild(element);
  382. // Always use ltr on the container, otherwise text-anchor will be
  383. // flipped and text appear outside labels, buttons, tooltip etc (#3482)
  384. attr(container, 'dir', 'ltr');
  385. // For browsers other than IE, add the namespace attribute (#1978)
  386. if (container.innerHTML.indexOf('xmlns') === -1) {
  387. attr(element, 'xmlns', this.SVG_NS);
  388. }
  389. // object properties
  390. renderer.isSVG = true;
  391. this.box = element;
  392. this.boxWrapper = boxWrapper;
  393. renderer.alignedObjects = [];
  394. // #24, #672, #1070
  395. this.url = ((isFirefox || isWebKit) &&
  396. doc.getElementsByTagName('base').length) ?
  397. win.location.href
  398. .split('#')[0] // remove the hash
  399. .replace(/<[^>]*>/g, '') // wing cut HTML
  400. // escape parantheses and quotes
  401. .replace(/([\('\)])/g, '\\$1')
  402. // replace spaces (needed for Safari only)
  403. .replace(/ /g, '%20') :
  404. '';
  405. // Add description
  406. desc = this.createElement('desc').add();
  407. desc.element.appendChild(doc.createTextNode('Created with Highcharts 8.1.2'));
  408. renderer.defs = this.createElement('defs').add();
  409. renderer.allowHTML = allowHTML;
  410. renderer.forExport = forExport;
  411. renderer.styledMode = styledMode;
  412. renderer.gradients = {}; // Object where gradient SvgElements are stored
  413. renderer.cache = {}; // Cache for numerical bounding boxes
  414. renderer.cacheKeys = [];
  415. renderer.imgCount = 0;
  416. renderer.setSize(width, height, false);
  417. // Issue 110 workaround:
  418. // In Firefox, if a div is positioned by percentage, its pixel position
  419. // may land between pixels. The container itself doesn't display this,
  420. // but an SVG element inside this container will be drawn at subpixel
  421. // precision. In order to draw sharp lines, this must be compensated
  422. // for. This doesn't seem to work inside iframes though (like in
  423. // jsFiddle).
  424. var subPixelFix, rect;
  425. if (isFirefox && container.getBoundingClientRect) {
  426. subPixelFix = function () {
  427. css(container, { left: 0, top: 0 });
  428. rect = container.getBoundingClientRect();
  429. css(container, {
  430. left: (Math.ceil(rect.left) - rect.left) + 'px',
  431. top: (Math.ceil(rect.top) - rect.top) + 'px'
  432. });
  433. };
  434. // run the fix now
  435. subPixelFix();
  436. // run it on resize
  437. renderer.unSubPixelFix = addEvent(win, 'resize', subPixelFix);
  438. }
  439. };
  440. /**
  441. * General method for adding a definition to the SVG `defs` tag. Can be used
  442. * for gradients, fills, filters etc. Styled mode only. A hook for adding
  443. * general definitions to the SVG's defs tag. Definitions can be referenced
  444. * from the CSS by its `id`. Read more in
  445. * [gradients, shadows and patterns](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns).
  446. * Styled mode only.
  447. *
  448. * @function Highcharts.SVGRenderer#definition
  449. *
  450. * @param {Highcharts.SVGDefinitionObject} def
  451. * A serialized form of an SVG definition, including children.
  452. *
  453. * @return {Highcharts.SVGElement}
  454. * The inserted node.
  455. */
  456. SVGRenderer.prototype.definition = function (def) {
  457. var ren = this;
  458. /**
  459. * @private
  460. * @param {Highcharts.SVGDefinitionObject} config - SVG definition
  461. * @param {Highcharts.SVGElement} [parent] - parent node
  462. */
  463. function recurse(config, parent) {
  464. var ret;
  465. splat(config).forEach(function (item) {
  466. var node = ren.createElement(item.tagName), attr = {};
  467. // Set attributes
  468. objectEach(item, function (val, key) {
  469. if (key !== 'tagName' &&
  470. key !== 'children' &&
  471. key !== 'textContent') {
  472. attr[key] = val;
  473. }
  474. });
  475. node.attr(attr);
  476. // Add to the tree
  477. node.add(parent || ren.defs);
  478. // Add text content
  479. if (item.textContent) {
  480. node.element.appendChild(doc.createTextNode(item.textContent));
  481. }
  482. // Recurse
  483. recurse(item.children || [], node);
  484. ret = node;
  485. });
  486. // Return last node added (on top level it's the only one)
  487. return ret;
  488. }
  489. return recurse(def);
  490. };
  491. /**
  492. * Get the global style setting for the renderer.
  493. *
  494. * @private
  495. * @function Highcharts.SVGRenderer#getStyle
  496. *
  497. * @param {Highcharts.CSSObject} style
  498. * Style settings.
  499. *
  500. * @return {Highcharts.CSSObject}
  501. * The style settings mixed with defaults.
  502. */
  503. SVGRenderer.prototype.getStyle = function (style) {
  504. this.style = extend({
  505. fontFamily: '"Lucida Grande", "Lucida Sans Unicode", ' +
  506. 'Arial, Helvetica, sans-serif',
  507. fontSize: '12px'
  508. }, style);
  509. return this.style;
  510. };
  511. /**
  512. * Apply the global style on the renderer, mixed with the default styles.
  513. *
  514. * @function Highcharts.SVGRenderer#setStyle
  515. *
  516. * @param {Highcharts.CSSObject} style
  517. * CSS to apply.
  518. */
  519. SVGRenderer.prototype.setStyle = function (style) {
  520. this.boxWrapper.css(this.getStyle(style));
  521. };
  522. /**
  523. * Detect whether the renderer is hidden. This happens when one of the
  524. * parent elements has `display: none`. Used internally to detect when we
  525. * needto render preliminarily in another div to get the text bounding boxes
  526. * right.
  527. *
  528. * @function Highcharts.SVGRenderer#isHidden
  529. *
  530. * @return {boolean}
  531. * True if it is hidden.
  532. */
  533. SVGRenderer.prototype.isHidden = function () {
  534. return !this.boxWrapper.getBBox().width;
  535. };
  536. /**
  537. * Destroys the renderer and its allocated members.
  538. *
  539. * @function Highcharts.SVGRenderer#destroy
  540. *
  541. * @return {null}
  542. */
  543. SVGRenderer.prototype.destroy = function () {
  544. var renderer = this, rendererDefs = renderer.defs;
  545. renderer.box = null;
  546. renderer.boxWrapper = renderer.boxWrapper.destroy();
  547. // Call destroy on all gradient elements
  548. destroyObjectProperties(renderer.gradients || {});
  549. renderer.gradients = null;
  550. // Defs are null in VMLRenderer
  551. // Otherwise, destroy them here.
  552. if (rendererDefs) {
  553. renderer.defs = rendererDefs.destroy();
  554. }
  555. // Remove sub pixel fix handler (#982)
  556. if (renderer.unSubPixelFix) {
  557. renderer.unSubPixelFix();
  558. }
  559. renderer.alignedObjects = null;
  560. return null;
  561. };
  562. /**
  563. * Create a wrapper for an SVG element. Serves as a factory for
  564. * {@link SVGElement}, but this function is itself mostly called from
  565. * primitive factories like {@link SVGRenderer#path}, {@link
  566. * SVGRenderer#rect} or {@link SVGRenderer#text}.
  567. *
  568. * @function Highcharts.SVGRenderer#createElement
  569. *
  570. * @param {string} nodeName
  571. * The node name, for example `rect`, `g` etc.
  572. *
  573. * @return {Highcharts.SVGElement}
  574. * The generated SVGElement.
  575. */
  576. SVGRenderer.prototype.createElement = function (nodeName) {
  577. var wrapper = new this.Element();
  578. wrapper.init(this, nodeName);
  579. return wrapper;
  580. };
  581. /**
  582. * Get converted radial gradient attributes according to the radial
  583. * reference. Used internally from the {@link SVGElement#colorGradient}
  584. * function.
  585. *
  586. * @private
  587. * @function Highcharts.SVGRenderer#getRadialAttr
  588. */
  589. SVGRenderer.prototype.getRadialAttr = function (radialReference, gradAttr) {
  590. return {
  591. cx: (radialReference[0] - radialReference[2] / 2) +
  592. gradAttr.cx * radialReference[2],
  593. cy: (radialReference[1] - radialReference[2] / 2) +
  594. gradAttr.cy * radialReference[2],
  595. r: gradAttr.r * radialReference[2]
  596. };
  597. };
  598. /**
  599. * Truncate the text node contents to a given length. Used when the css
  600. * width is set. If the `textOverflow` is `ellipsis`, the text is truncated
  601. * character by character to the given length. If not, the text is
  602. * word-wrapped line by line.
  603. *
  604. * @private
  605. * @function Highcharts.SVGRenderer#truncate
  606. *
  607. * @return {boolean}
  608. * True if tspan is too long.
  609. */
  610. SVGRenderer.prototype.truncate = function (wrapper, tspan, text, words, startAt, width, getString) {
  611. var renderer = this, rotation = wrapper.rotation, str,
  612. // Word wrap can not be truncated to shorter than one word, ellipsis
  613. // text can be completely blank.
  614. minIndex = words ? 1 : 0, maxIndex = (text || words).length, currentIndex = maxIndex,
  615. // Cache the lengths to avoid checking the same twice
  616. lengths = [], updateTSpan = function (s) {
  617. if (tspan.firstChild) {
  618. tspan.removeChild(tspan.firstChild);
  619. }
  620. if (s) {
  621. tspan.appendChild(doc.createTextNode(s));
  622. }
  623. }, getSubStringLength = function (charEnd, concatenatedEnd) {
  624. // charEnd is useed when finding the character-by-character
  625. // break for ellipsis, concatenatedEnd is used for word-by-word
  626. // break for word wrapping.
  627. var end = concatenatedEnd || charEnd;
  628. if (typeof lengths[end] === 'undefined') {
  629. // Modern browsers
  630. if (tspan.getSubStringLength) {
  631. // Fails with DOM exception on unit-tests/legend/members
  632. // of unknown reason. Desired width is 0, text content
  633. // is "5" and end is 1.
  634. try {
  635. lengths[end] = startAt +
  636. tspan.getSubStringLength(0, words ? end + 1 : end);
  637. }
  638. catch (e) {
  639. '';
  640. }
  641. // Legacy
  642. }
  643. else if (renderer.getSpanWidth) { // #9058 jsdom
  644. updateTSpan(getString(text || words, charEnd));
  645. lengths[end] = startAt +
  646. renderer.getSpanWidth(wrapper, tspan);
  647. }
  648. }
  649. return lengths[end];
  650. }, actualWidth, truncated;
  651. wrapper.rotation = 0; // discard rotation when computing box
  652. actualWidth = getSubStringLength(tspan.textContent.length);
  653. truncated = startAt + actualWidth > width;
  654. if (truncated) {
  655. // Do a binary search for the index where to truncate the text
  656. while (minIndex <= maxIndex) {
  657. currentIndex = Math.ceil((minIndex + maxIndex) / 2);
  658. // When checking words for word-wrap, we need to build the
  659. // string and measure the subStringLength at the concatenated
  660. // word length.
  661. if (words) {
  662. str = getString(words, currentIndex);
  663. }
  664. actualWidth = getSubStringLength(currentIndex, str && str.length - 1);
  665. if (minIndex === maxIndex) {
  666. // Complete
  667. minIndex = maxIndex + 1;
  668. }
  669. else if (actualWidth > width) {
  670. // Too large. Set max index to current.
  671. maxIndex = currentIndex - 1;
  672. }
  673. else {
  674. // Within width. Set min index to current.
  675. minIndex = currentIndex;
  676. }
  677. }
  678. // If max index was 0 it means the shortest possible text was also
  679. // too large. For ellipsis that means only the ellipsis, while for
  680. // word wrap it means the whole first word.
  681. if (maxIndex === 0) {
  682. // Remove ellipsis
  683. updateTSpan('');
  684. // If the new text length is one less than the original, we don't
  685. // need the ellipsis
  686. }
  687. else if (!(text && maxIndex === text.length - 1)) {
  688. updateTSpan(str || getString(text || words, currentIndex));
  689. }
  690. }
  691. // When doing line wrapping, prepare for the next line by removing the
  692. // items from this line.
  693. if (words) {
  694. words.splice(0, currentIndex);
  695. }
  696. wrapper.actualWidth = actualWidth;
  697. wrapper.rotation = rotation; // Apply rotation again.
  698. return truncated;
  699. };
  700. /**
  701. * Parse a simple HTML string into SVG tspans. Called internally when text
  702. * is set on an SVGElement. The function supports a subset of HTML tags, CSS
  703. * text features like `width`, `text-overflow`, `white-space`, and also
  704. * attributes like `href` and `style`.
  705. *
  706. * @private
  707. * @function Highcharts.SVGRenderer#buildText
  708. *
  709. * @param {Highcharts.SVGElement} wrapper
  710. * The parent SVGElement.
  711. */
  712. SVGRenderer.prototype.buildText = function (wrapper) {
  713. 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) {
  714. var fontSizeStyle;
  715. if (!renderer.styledMode) {
  716. fontSizeStyle =
  717. /(px|em)$/.test(tspan && tspan.style.fontSize) ?
  718. tspan.style.fontSize :
  719. (fontSize || renderer.style.fontSize || 12);
  720. }
  721. return textLineHeight ?
  722. pInt(textLineHeight) :
  723. renderer.fontMetrics(fontSizeStyle,
  724. // Get the computed size from parent if not explicit
  725. (tspan.getAttribute('style') ? tspan : textNode)).h;
  726. }, unescapeEntities = function (inputStr, except) {
  727. objectEach(renderer.escapes, function (value, key) {
  728. if (!except || except.indexOf(value) === -1) {
  729. inputStr = inputStr.toString().replace(new RegExp(value, 'g'), key);
  730. }
  731. });
  732. return inputStr;
  733. }, parseAttribute = function (s, attr) {
  734. var start, delimiter;
  735. start = s.indexOf('<');
  736. s = s.substring(start, s.indexOf('>') - start);
  737. start = s.indexOf(attr + '=');
  738. if (start !== -1) {
  739. start = start + attr.length + 1;
  740. delimiter = s.charAt(start);
  741. if (delimiter === '"' || delimiter === "'") { // eslint-disable-line quotes
  742. s = s.substring(start + 1);
  743. return s.substring(0, s.indexOf(delimiter));
  744. }
  745. }
  746. };
  747. var regexMatchBreaks = /<br.*?>/g;
  748. // The buildText code is quite heavy, so if we're not changing something
  749. // that affects the text, skip it (#6113).
  750. textCache = [
  751. textStr,
  752. ellipsis,
  753. noWrap,
  754. textLineHeight,
  755. textOutline,
  756. fontSize,
  757. width
  758. ].join(',');
  759. if (textCache === wrapper.textCache) {
  760. return;
  761. }
  762. wrapper.textCache = textCache;
  763. // Remove old text
  764. while (i--) {
  765. textNode.removeChild(childNodes[i]);
  766. }
  767. // Skip tspans, add text directly to text node. The forceTSpan is a hook
  768. // used in text outline hack.
  769. if (!hasMarkup &&
  770. !textOutline &&
  771. !ellipsis &&
  772. !width &&
  773. (textStr.indexOf(' ') === -1 ||
  774. (noWrap && !regexMatchBreaks.test(textStr)))) {
  775. textNode.appendChild(doc.createTextNode(unescapeEntities(textStr)));
  776. // Complex strings, add more logic
  777. }
  778. else {
  779. if (tempParent) {
  780. // attach it to the DOM to read offset width
  781. tempParent.appendChild(textNode);
  782. }
  783. if (hasMarkup) {
  784. lines = renderer.styledMode ? (textStr
  785. .replace(/<(b|strong)>/g, '<span class="highcharts-strong">')
  786. .replace(/<(i|em)>/g, '<span class="highcharts-emphasized">')) : (textStr
  787. .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
  788. .replace(/<(i|em)>/g, '<span style="font-style:italic">'));
  789. lines = lines
  790. .replace(/<a/g, '<span')
  791. .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
  792. .split(regexMatchBreaks);
  793. }
  794. else {
  795. lines = [textStr];
  796. }
  797. // Trim empty lines (#5261)
  798. lines = lines.filter(function (line) {
  799. return line !== '';
  800. });
  801. // build the lines
  802. lines.forEach(function (line, lineNo) {
  803. var spans, spanNo = 0, lineLength = 0;
  804. line = line
  805. // Trim to prevent useless/costly process on the spaces
  806. // (#5258)
  807. .replace(/^\s+|\s+$/g, '')
  808. .replace(/<span/g, '|||<span')
  809. .replace(/<\/span>/g, '</span>|||');
  810. spans = line.split('|||');
  811. spans.forEach(function buildTextSpans(span) {
  812. if (span !== '' || spans.length === 1) {
  813. var attributes = {}, tspan = doc.createElementNS(renderer.SVG_NS, 'tspan'), a, classAttribute, styleAttribute, // #390
  814. hrefAttribute;
  815. classAttribute = parseAttribute(span, 'class');
  816. if (classAttribute) {
  817. attr(tspan, 'class', classAttribute);
  818. }
  819. styleAttribute = parseAttribute(span, 'style');
  820. if (styleAttribute) {
  821. styleAttribute = styleAttribute.replace(/(;| |^)color([ :])/, '$1fill$2');
  822. attr(tspan, 'style', styleAttribute);
  823. }
  824. // For anchors, wrap the tspan in an <a> tag and apply
  825. // the href attribute as is (#13559). Not for export
  826. // (#1529)
  827. hrefAttribute = parseAttribute(span, 'href');
  828. if (hrefAttribute && !forExport) {
  829. if (
  830. // Stop JavaScript links, vulnerable to XSS
  831. hrefAttribute.split(':')[0].toLowerCase()
  832. .indexOf('javascript') === -1) {
  833. a = doc.createElementNS(renderer.SVG_NS, 'a');
  834. attr(a, 'href', hrefAttribute);
  835. attr(tspan, 'class', 'highcharts-anchor');
  836. a.appendChild(tspan);
  837. if (!renderer.styledMode) {
  838. css(tspan, { cursor: 'pointer' });
  839. }
  840. }
  841. }
  842. // Strip away unsupported HTML tags (#7126)
  843. span = unescapeEntities(span.replace(/<[a-zA-Z\/](.|\n)*?>/g, '') || ' ');
  844. // Nested tags aren't supported, and cause crash in
  845. // Safari (#1596)
  846. if (span !== ' ') {
  847. // add the text node
  848. tspan.appendChild(doc.createTextNode(span));
  849. // First span in a line, align it to the left
  850. if (!spanNo) {
  851. if (lineNo && parentX !== null) {
  852. attributes.x = parentX;
  853. }
  854. }
  855. else {
  856. attributes.dx = 0; // #16
  857. }
  858. // add attributes
  859. attr(tspan, attributes);
  860. // Append it
  861. textNode.appendChild(a || tspan);
  862. // first span on subsequent line, add the line
  863. // height
  864. if (!spanNo && isSubsequentLine) {
  865. // allow getting the right offset height in
  866. // exporting in IE
  867. if (!svg && forExport) {
  868. css(tspan, { display: 'block' });
  869. }
  870. // Set the line height based on the font size of
  871. // either the text element or the tspan element
  872. attr(tspan, 'dy', getLineHeight(tspan));
  873. }
  874. // Check width and apply soft breaks or ellipsis
  875. if (width) {
  876. var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
  877. hasWhiteSpace = !noWrap && (spans.length > 1 ||
  878. lineNo ||
  879. words.length > 1), wrapLineNo = 0, dy = getLineHeight(tspan);
  880. if (ellipsis) {
  881. truncated = renderer.truncate(wrapper, tspan, span, void 0, 0,
  882. // Target width
  883. Math.max(0,
  884. // Substract the font face to make
  885. // room for the ellipsis itself
  886. width - parseInt(fontSize || 12, 10)),
  887. // Build the text to test for
  888. function (text, currentIndex) {
  889. return text.substring(0, currentIndex) + '\u2026';
  890. });
  891. }
  892. else if (hasWhiteSpace) {
  893. while (words.length) {
  894. // For subsequent lines, create tspans
  895. // with the same style attributes as the
  896. // parent text node.
  897. if (words.length &&
  898. !noWrap &&
  899. wrapLineNo > 0) {
  900. tspan = doc.createElementNS(SVG_NS, 'tspan');
  901. attr(tspan, {
  902. dy: dy,
  903. x: parentX
  904. });
  905. if (styleAttribute) { // #390
  906. attr(tspan, 'style', styleAttribute);
  907. }
  908. // Start by appending the full
  909. // remaining text
  910. tspan.appendChild(doc.createTextNode(words.join(' ')
  911. .replace(/- /g, '-')));
  912. textNode.appendChild(tspan);
  913. }
  914. // For each line, truncate the remaining
  915. // words into the line length.
  916. renderer.truncate(wrapper, tspan, null, words, wrapLineNo === 0 ? lineLength : 0, width,
  917. // Build the text to test for
  918. function (text, currentIndex) {
  919. return words
  920. .slice(0, currentIndex)
  921. .join(' ')
  922. .replace(/- /g, '-');
  923. });
  924. lineLength = wrapper.actualWidth;
  925. wrapLineNo++;
  926. }
  927. }
  928. }
  929. spanNo++;
  930. }
  931. }
  932. });
  933. // To avoid beginning lines that doesn't add to the textNode
  934. // (#6144)
  935. isSubsequentLine = (isSubsequentLine ||
  936. textNode.childNodes.length);
  937. });
  938. if (ellipsis && truncated) {
  939. wrapper.attr('title', unescapeEntities(wrapper.textStr || '', ['&lt;', '&gt;']) // #7179
  940. );
  941. }
  942. if (tempParent) {
  943. tempParent.removeChild(textNode);
  944. }
  945. // Apply the text outline
  946. if (isString(textOutline) && wrapper.applyTextOutline) {
  947. wrapper.applyTextOutline(textOutline);
  948. }
  949. }
  950. };
  951. /**
  952. * Returns white for dark colors and black for bright colors.
  953. *
  954. * @function Highcharts.SVGRenderer#getContrast
  955. *
  956. * @param {Highcharts.ColorString} rgba
  957. * The color to get the contrast for.
  958. *
  959. * @return {Highcharts.ColorString}
  960. * The contrast color, either `#000000` or `#FFFFFF`.
  961. */
  962. SVGRenderer.prototype.getContrast = function (rgba) {
  963. rgba = Color.parse(rgba).rgba;
  964. // The threshold may be discussed. Here's a proposal for adding
  965. // different weight to the color channels (#6216)
  966. rgba[0] *= 1; // red
  967. rgba[1] *= 1.2; // green
  968. rgba[2] *= 0.5; // blue
  969. return rgba[0] + rgba[1] + rgba[2] >
  970. 1.8 * 255 ?
  971. '#000000' :
  972. '#FFFFFF';
  973. };
  974. /**
  975. * Create a button with preset states.
  976. *
  977. * @function Highcharts.SVGRenderer#button
  978. *
  979. * @param {string} text
  980. * The text or HTML to draw.
  981. *
  982. * @param {number} x
  983. * The x position of the button's left side.
  984. *
  985. * @param {number} y
  986. * The y position of the button's top side.
  987. *
  988. * @param {Highcharts.EventCallbackFunction<Highcharts.SVGElement>} callback
  989. * The function to execute on button click or touch.
  990. *
  991. * @param {Highcharts.SVGAttributes} [normalState]
  992. * SVG attributes for the normal state.
  993. *
  994. * @param {Highcharts.SVGAttributes} [hoverState]
  995. * SVG attributes for the hover state.
  996. *
  997. * @param {Highcharts.SVGAttributes} [pressedState]
  998. * SVG attributes for the pressed state.
  999. *
  1000. * @param {Highcharts.SVGAttributes} [disabledState]
  1001. * SVG attributes for the disabled state.
  1002. *
  1003. * @param {Highcharts.SymbolKeyValue} [shape=rect]
  1004. * The shape type.
  1005. *
  1006. * @param {boolean} [useHTML=false]
  1007. * Wether to use HTML to render the label.
  1008. *
  1009. * @return {Highcharts.SVGElement}
  1010. * The button element.
  1011. */
  1012. SVGRenderer.prototype.button = function (text, x, y, callback, normalState, hoverState, pressedState, disabledState, shape, useHTML) {
  1013. var label = this.label(text, x, y, shape, void 0, void 0, useHTML, void 0, 'button'), curState = 0, styledMode = this.styledMode, userNormalStyle = normalState && normalState.style || {};
  1014. // Remove stylable attributes
  1015. if (normalState && normalState.style) {
  1016. delete normalState.style;
  1017. }
  1018. // Default, non-stylable attributes
  1019. label.attr(merge({ padding: 8, r: 2 }, normalState));
  1020. if (!styledMode) {
  1021. // Presentational
  1022. var normalStyle, hoverStyle, pressedStyle, disabledStyle;
  1023. // Normal state - prepare the attributes
  1024. normalState = merge({
  1025. fill: '#f7f7f7',
  1026. stroke: '#cccccc',
  1027. 'stroke-width': 1,
  1028. style: {
  1029. color: '#333333',
  1030. cursor: 'pointer',
  1031. fontWeight: 'normal'
  1032. }
  1033. }, {
  1034. style: userNormalStyle
  1035. }, normalState);
  1036. normalStyle = normalState.style;
  1037. delete normalState.style;
  1038. // Hover state
  1039. hoverState = merge(normalState, {
  1040. fill: '#e6e6e6'
  1041. }, hoverState);
  1042. hoverStyle = hoverState.style;
  1043. delete hoverState.style;
  1044. // Pressed state
  1045. pressedState = merge(normalState, {
  1046. fill: '#e6ebf5',
  1047. style: {
  1048. color: '#000000',
  1049. fontWeight: 'bold'
  1050. }
  1051. }, pressedState);
  1052. pressedStyle = pressedState.style;
  1053. delete pressedState.style;
  1054. // Disabled state
  1055. disabledState = merge(normalState, {
  1056. style: {
  1057. color: '#cccccc'
  1058. }
  1059. }, disabledState);
  1060. disabledStyle = disabledState.style;
  1061. delete disabledState.style;
  1062. }
  1063. // Add the events. IE9 and IE10 need mouseover and mouseout to funciton
  1064. // (#667).
  1065. addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function () {
  1066. if (curState !== 3) {
  1067. label.setState(1);
  1068. }
  1069. });
  1070. addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function () {
  1071. if (curState !== 3) {
  1072. label.setState(curState);
  1073. }
  1074. });
  1075. label.setState = function (state) {
  1076. // Hover state is temporary, don't record it
  1077. if (state !== 1) {
  1078. label.state = curState = state;
  1079. }
  1080. // Update visuals
  1081. label
  1082. .removeClass(/highcharts-button-(normal|hover|pressed|disabled)/)
  1083. .addClass('highcharts-button-' +
  1084. ['normal', 'hover', 'pressed', 'disabled'][state || 0]);
  1085. if (!styledMode) {
  1086. label
  1087. .attr([
  1088. normalState,
  1089. hoverState,
  1090. pressedState,
  1091. disabledState
  1092. ][state || 0])
  1093. .css([
  1094. normalStyle,
  1095. hoverStyle,
  1096. pressedStyle,
  1097. disabledStyle
  1098. ][state || 0]);
  1099. }
  1100. };
  1101. // Presentational attributes
  1102. if (!styledMode) {
  1103. label
  1104. .attr(normalState)
  1105. .css(extend({ cursor: 'default' }, normalStyle));
  1106. }
  1107. return label
  1108. .on('click', function (e) {
  1109. if (curState !== 3) {
  1110. callback.call(label, e);
  1111. }
  1112. });
  1113. };
  1114. /**
  1115. * Make a straight line crisper by not spilling out to neighbour pixels.
  1116. *
  1117. * @function Highcharts.SVGRenderer#crispLine
  1118. *
  1119. * @param {Highcharts.SVGPathArray} points
  1120. * The original points on the format `[['M', 0, 0], ['L', 100, 0]]`.
  1121. *
  1122. * @param {number} width
  1123. * The width of the line.
  1124. *
  1125. * @param {string} roundingFunction
  1126. * The rounding function name on the `Math` object, can be one of
  1127. * `round`, `floor` or `ceil`.
  1128. *
  1129. * @return {Highcharts.SVGPathArray}
  1130. * The original points array, but modified to render crisply.
  1131. */
  1132. SVGRenderer.prototype.crispLine = function (points, width, roundingFunction) {
  1133. if (roundingFunction === void 0) { roundingFunction = 'round'; }
  1134. var start = points[0];
  1135. var end = points[1];
  1136. // Normalize to a crisp line
  1137. if (start[1] === end[1]) {
  1138. // Substract due to #1129. Now bottom and left axis gridlines behave
  1139. // the same.
  1140. start[1] = end[1] =
  1141. Math[roundingFunction](start[1]) - (width % 2 / 2);
  1142. }
  1143. if (start[2] === end[2]) {
  1144. start[2] = end[2] =
  1145. Math[roundingFunction](start[2]) + (width % 2 / 2);
  1146. }
  1147. return points;
  1148. };
  1149. /**
  1150. * Draw a path, wraps the SVG `path` element.
  1151. *
  1152. * @sample highcharts/members/renderer-path-on-chart/
  1153. * Draw a path in a chart
  1154. * @sample highcharts/members/renderer-path/
  1155. * Draw a path independent from a chart
  1156. *
  1157. * @example
  1158. * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z'])
  1159. * .attr({ stroke: '#ff00ff' })
  1160. * .add();
  1161. *
  1162. * @function Highcharts.SVGRenderer#path
  1163. *
  1164. * @param {Highcharts.SVGPathArray} [path]
  1165. * An SVG path definition in array form.
  1166. *
  1167. * @return {Highcharts.SVGElement}
  1168. * The generated wrapper element.
  1169. *
  1170. */ /**
  1171. * Draw a path, wraps the SVG `path` element.
  1172. *
  1173. * @function Highcharts.SVGRenderer#path
  1174. *
  1175. * @param {Highcharts.SVGAttributes} [attribs]
  1176. * The initial attributes.
  1177. *
  1178. * @return {Highcharts.SVGElement}
  1179. * The generated wrapper element.
  1180. */
  1181. SVGRenderer.prototype.path = function (path) {
  1182. var attribs = (this.styledMode ? {} : {
  1183. fill: 'none'
  1184. });
  1185. if (isArray(path)) {
  1186. attribs.d = path;
  1187. }
  1188. else if (isObject(path)) { // attributes
  1189. extend(attribs, path);
  1190. }
  1191. return this.createElement('path').attr(attribs);
  1192. };
  1193. /**
  1194. * Draw a circle, wraps the SVG `circle` element.
  1195. *
  1196. * @sample highcharts/members/renderer-circle/
  1197. * Drawing a circle
  1198. *
  1199. * @function Highcharts.SVGRenderer#circle
  1200. *
  1201. * @param {number} [x]
  1202. * The center x position.
  1203. *
  1204. * @param {number} [y]
  1205. * The center y position.
  1206. *
  1207. * @param {number} [r]
  1208. * The radius.
  1209. *
  1210. * @return {Highcharts.SVGElement}
  1211. * The generated wrapper element.
  1212. */ /**
  1213. * Draw a circle, wraps the SVG `circle` element.
  1214. *
  1215. * @function Highcharts.SVGRenderer#circle
  1216. *
  1217. * @param {Highcharts.SVGAttributes} [attribs]
  1218. * The initial attributes.
  1219. *
  1220. * @return {Highcharts.SVGElement}
  1221. * The generated wrapper element.
  1222. */
  1223. SVGRenderer.prototype.circle = function (x, y, r) {
  1224. var attribs = (isObject(x) ?
  1225. x :
  1226. typeof x === 'undefined' ? {} : { x: x, y: y, r: r }), wrapper = this.createElement('circle');
  1227. // Setting x or y translates to cx and cy
  1228. wrapper.xSetter = wrapper.ySetter = function (value, key, element) {
  1229. element.setAttribute('c' + key, value);
  1230. };
  1231. return wrapper.attr(attribs);
  1232. };
  1233. /**
  1234. * Draw and return an arc.
  1235. *
  1236. * @sample highcharts/members/renderer-arc/
  1237. * Drawing an arc
  1238. *
  1239. * @function Highcharts.SVGRenderer#arc
  1240. *
  1241. * @param {number} [x=0]
  1242. * Center X position.
  1243. *
  1244. * @param {number} [y=0]
  1245. * Center Y position.
  1246. *
  1247. * @param {number} [r=0]
  1248. * The outer radius' of the arc.
  1249. *
  1250. * @param {number} [innerR=0]
  1251. * Inner radius like used in donut charts.
  1252. *
  1253. * @param {number} [start=0]
  1254. * The starting angle of the arc in radians, where 0 is to the right and
  1255. * `-Math.PI/2` is up.
  1256. *
  1257. * @param {number} [end=0]
  1258. * The ending angle of the arc in radians, where 0 is to the right and
  1259. * `-Math.PI/2` is up.
  1260. *
  1261. * @return {Highcharts.SVGElement}
  1262. * The generated wrapper element.
  1263. */ /**
  1264. * Draw and return an arc. Overloaded function that takes arguments object.
  1265. *
  1266. * @function Highcharts.SVGRenderer#arc
  1267. *
  1268. * @param {Highcharts.SVGAttributes} attribs
  1269. * Initial SVG attributes.
  1270. *
  1271. * @return {Highcharts.SVGElement}
  1272. * The generated wrapper element.
  1273. */
  1274. SVGRenderer.prototype.arc = function (x, y, r, innerR, start, end) {
  1275. var arc, options;
  1276. if (isObject(x)) {
  1277. options = x;
  1278. y = options.y;
  1279. r = options.r;
  1280. innerR = options.innerR;
  1281. start = options.start;
  1282. end = options.end;
  1283. x = options.x;
  1284. }
  1285. else {
  1286. options = {
  1287. innerR: innerR,
  1288. start: start,
  1289. end: end
  1290. };
  1291. }
  1292. // Arcs are defined as symbols for the ability to set
  1293. // attributes in attr and animate
  1294. arc = this.symbol('arc', x, y, r, r, options);
  1295. arc.r = r; // #959
  1296. return arc;
  1297. };
  1298. /**
  1299. * Draw and return a rectangle.
  1300. *
  1301. * @function Highcharts.SVGRenderer#rect
  1302. *
  1303. * @param {number} [x]
  1304. * Left position.
  1305. *
  1306. * @param {number} [y]
  1307. * Top position.
  1308. *
  1309. * @param {number} [width]
  1310. * Width of the rectangle.
  1311. *
  1312. * @param {number} [height]
  1313. * Height of the rectangle.
  1314. *
  1315. * @param {number} [r]
  1316. * Border corner radius.
  1317. *
  1318. * @param {number} [strokeWidth]
  1319. * A stroke width can be supplied to allow crisp drawing.
  1320. *
  1321. * @return {Highcharts.SVGElement}
  1322. * The generated wrapper element.
  1323. */ /**
  1324. * Draw and return a rectangle.
  1325. *
  1326. * @sample highcharts/members/renderer-rect-on-chart/
  1327. * Draw a rectangle in a chart
  1328. * @sample highcharts/members/renderer-rect/
  1329. * Draw a rectangle independent from a chart
  1330. *
  1331. * @function Highcharts.SVGRenderer#rect
  1332. *
  1333. * @param {Highcharts.SVGAttributes} [attributes]
  1334. * General SVG attributes for the rectangle.
  1335. *
  1336. * @return {Highcharts.SVGElement}
  1337. * The generated wrapper element.
  1338. */
  1339. SVGRenderer.prototype.rect = function (x, y, width, height, r, strokeWidth) {
  1340. r = isObject(x) ? x.r : r;
  1341. var wrapper = this.createElement('rect'), attribs = isObject(x) ?
  1342. x :
  1343. typeof x === 'undefined' ?
  1344. {} :
  1345. {
  1346. x: x,
  1347. y: y,
  1348. width: Math.max(width, 0),
  1349. height: Math.max(height, 0)
  1350. };
  1351. if (!this.styledMode) {
  1352. if (typeof strokeWidth !== 'undefined') {
  1353. attribs.strokeWidth = strokeWidth;
  1354. attribs = wrapper.crisp(attribs);
  1355. }
  1356. attribs.fill = 'none';
  1357. }
  1358. if (r) {
  1359. attribs.r = r;
  1360. }
  1361. wrapper.rSetter = function (value, key, element) {
  1362. wrapper.r = value;
  1363. attr(element, {
  1364. rx: value,
  1365. ry: value
  1366. });
  1367. };
  1368. wrapper.rGetter = function () {
  1369. return wrapper.r;
  1370. };
  1371. return wrapper.attr(attribs);
  1372. };
  1373. /**
  1374. * Resize the {@link SVGRenderer#box} and re-align all aligned child
  1375. * elements.
  1376. *
  1377. * @sample highcharts/members/renderer-g/
  1378. * Show and hide grouped objects
  1379. *
  1380. * @function Highcharts.SVGRenderer#setSize
  1381. *
  1382. * @param {number} width
  1383. * The new pixel width.
  1384. *
  1385. * @param {number} height
  1386. * The new pixel height.
  1387. *
  1388. * @param {boolean|Highcharts.AnimationOptionsObject} [animate=true]
  1389. * Whether and how to animate.
  1390. */
  1391. SVGRenderer.prototype.setSize = function (width, height, animate) {
  1392. var renderer = this, alignedObjects = renderer.alignedObjects, i = alignedObjects.length;
  1393. renderer.width = width;
  1394. renderer.height = height;
  1395. renderer.boxWrapper.animate({
  1396. width: width,
  1397. height: height
  1398. }, {
  1399. step: function () {
  1400. this.attr({
  1401. viewBox: '0 0 ' + this.attr('width') + ' ' +
  1402. this.attr('height')
  1403. });
  1404. },
  1405. duration: pick(animate, true) ? void 0 : 0
  1406. });
  1407. while (i--) {
  1408. alignedObjects[i].align();
  1409. }
  1410. };
  1411. /**
  1412. * Create and return an svg group element. Child
  1413. * {@link Highcharts.SVGElement} objects are added to the group by using the
  1414. * group as the first parameter in {@link Highcharts.SVGElement#add|add()}.
  1415. *
  1416. * @function Highcharts.SVGRenderer#g
  1417. *
  1418. * @param {string} [name]
  1419. * The group will be given a class name of `highcharts-{name}`. This
  1420. * can be used for styling and scripting.
  1421. *
  1422. * @return {Highcharts.SVGElement}
  1423. * The generated wrapper element.
  1424. */
  1425. SVGRenderer.prototype.g = function (name) {
  1426. var elem = this.createElement('g');
  1427. return name ?
  1428. elem.attr({ 'class': 'highcharts-' + name }) :
  1429. elem;
  1430. };
  1431. /**
  1432. * Display an image.
  1433. *
  1434. * @sample highcharts/members/renderer-image-on-chart/
  1435. * Add an image in a chart
  1436. * @sample highcharts/members/renderer-image/
  1437. * Add an image independent of a chart
  1438. *
  1439. * @function Highcharts.SVGRenderer#image
  1440. *
  1441. * @param {string} src
  1442. * The image source.
  1443. *
  1444. * @param {number} [x]
  1445. * The X position.
  1446. *
  1447. * @param {number} [y]
  1448. * The Y position.
  1449. *
  1450. * @param {number} [width]
  1451. * The image width. If omitted, it defaults to the image file width.
  1452. *
  1453. * @param {number} [height]
  1454. * The image height. If omitted it defaults to the image file
  1455. * height.
  1456. *
  1457. * @param {Function} [onload]
  1458. * Event handler for image load.
  1459. *
  1460. * @return {Highcharts.SVGElement}
  1461. * The generated wrapper element.
  1462. */
  1463. SVGRenderer.prototype.image = function (src, x, y, width, height, onload) {
  1464. var attribs = { preserveAspectRatio: 'none' }, elemWrapper, dummy, setSVGImageSource = function (el, src) {
  1465. // Set the href in the xlink namespace
  1466. if (el.setAttributeNS) {
  1467. el.setAttributeNS('http://www.w3.org/1999/xlink', 'href', src);
  1468. }
  1469. else {
  1470. // could be exporting in IE
  1471. // using href throws "not supported" in ie7 and under,
  1472. // requries regex shim to fix later
  1473. el.setAttribute('hc-svg-href', src);
  1474. }
  1475. }, onDummyLoad = function (e) {
  1476. setSVGImageSource(elemWrapper.element, src);
  1477. onload.call(elemWrapper, e);
  1478. };
  1479. // optional properties
  1480. if (arguments.length > 1) {
  1481. extend(attribs, {
  1482. x: x,
  1483. y: y,
  1484. width: width,
  1485. height: height
  1486. });
  1487. }
  1488. elemWrapper = this.createElement('image').attr(attribs);
  1489. // Add load event if supplied
  1490. if (onload) {
  1491. // We have to use a dummy HTML image since IE support for SVG image
  1492. // load events is very buggy. First set a transparent src, wait for
  1493. // dummy to load, and then add the real src to the SVG image.
  1494. setSVGImageSource(elemWrapper.element, 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' /* eslint-disable-line */);
  1495. dummy = new win.Image();
  1496. addEvent(dummy, 'load', onDummyLoad);
  1497. dummy.src = src;
  1498. if (dummy.complete) {
  1499. onDummyLoad({});
  1500. }
  1501. }
  1502. else {
  1503. setSVGImageSource(elemWrapper.element, src);
  1504. }
  1505. return elemWrapper;
  1506. };
  1507. /**
  1508. * Draw a symbol out of pre-defined shape paths from
  1509. * {@link SVGRenderer#symbols}.
  1510. * It is used in Highcharts for point makers, which cake a `symbol` option,
  1511. * and label and button backgrounds like in the tooltip and stock flags.
  1512. *
  1513. * @function Highcharts.SVGRenderer#symbol
  1514. *
  1515. * @param {string} symbol
  1516. * The symbol name.
  1517. *
  1518. * @param {number} [x]
  1519. * The X coordinate for the top left position.
  1520. *
  1521. * @param {number} [y]
  1522. * The Y coordinate for the top left position.
  1523. *
  1524. * @param {number} [width]
  1525. * The pixel width.
  1526. *
  1527. * @param {number} [height]
  1528. * The pixel height.
  1529. *
  1530. * @param {Highcharts.SymbolOptionsObject} [options]
  1531. * Additional options, depending on the actual symbol drawn.
  1532. *
  1533. * @return {Highcharts.SVGElement}
  1534. */
  1535. SVGRenderer.prototype.symbol = function (symbol, x, y, width, height, options) {
  1536. var ren = this, obj, imageRegex = /^url\((.*?)\)$/, isImage = imageRegex.test(symbol), sym = (!isImage && (this.symbols[symbol] ? symbol : 'circle')),
  1537. // get the symbol definition function
  1538. symbolFn = (sym && this.symbols[sym]), path, imageSrc, centerImage;
  1539. if (symbolFn) {
  1540. // Check if there's a path defined for this symbol
  1541. if (typeof x === 'number') {
  1542. path = symbolFn.call(this.symbols, Math.round(x || 0), Math.round(y || 0), width || 0, height || 0, options);
  1543. }
  1544. obj = this.path(path);
  1545. if (!ren.styledMode) {
  1546. obj.attr('fill', 'none');
  1547. }
  1548. // expando properties for use in animate and attr
  1549. extend(obj, {
  1550. symbolName: sym,
  1551. x: x,
  1552. y: y,
  1553. width: width,
  1554. height: height
  1555. });
  1556. if (options) {
  1557. extend(obj, options);
  1558. }
  1559. // Image symbols
  1560. }
  1561. else if (isImage) {
  1562. imageSrc = symbol.match(imageRegex)[1];
  1563. // Create the image synchronously, add attribs async
  1564. obj = this.image(imageSrc);
  1565. // The image width is not always the same as the symbol width. The
  1566. // image may be centered within the symbol, as is the case when
  1567. // image shapes are used as label backgrounds, for example in flags.
  1568. obj.imgwidth = pick(symbolSizes[imageSrc] && symbolSizes[imageSrc].width, options && options.width);
  1569. obj.imgheight = pick(symbolSizes[imageSrc] && symbolSizes[imageSrc].height, options && options.height);
  1570. /**
  1571. * Set the size and position
  1572. */
  1573. centerImage = function () {
  1574. obj.attr({
  1575. width: obj.width,
  1576. height: obj.height
  1577. });
  1578. };
  1579. /**
  1580. * Width and height setters that take both the image's physical size
  1581. * and the label size into consideration, and translates the image
  1582. * to center within the label.
  1583. */
  1584. ['width', 'height'].forEach(function (key) {
  1585. obj[key + 'Setter'] = function (value, key) {
  1586. var attribs = {}, imgSize = this['img' + key], trans = key === 'width' ? 'translateX' : 'translateY';
  1587. this[key] = value;
  1588. if (defined(imgSize)) {
  1589. // Scale and center the image within its container.
  1590. // The name `backgroundSize` is taken from the CSS spec,
  1591. // but the value `within` is made up. Other possible
  1592. // values in the spec, `cover` and `contain`, can be
  1593. // implemented if needed.
  1594. if (options &&
  1595. options.backgroundSize === 'within' &&
  1596. this.width &&
  1597. this.height) {
  1598. imgSize = Math.round(imgSize * Math.min(this.width / this.imgwidth, this.height / this.imgheight));
  1599. }
  1600. if (this.element) {
  1601. this.element.setAttribute(key, imgSize);
  1602. }
  1603. if (!this.alignByTranslate) {
  1604. attribs[trans] = ((this[key] || 0) - imgSize) / 2;
  1605. this.attr(attribs);
  1606. }
  1607. }
  1608. };
  1609. });
  1610. if (defined(x)) {
  1611. obj.attr({
  1612. x: x,
  1613. y: y
  1614. });
  1615. }
  1616. obj.isImg = true;
  1617. if (defined(obj.imgwidth) && defined(obj.imgheight)) {
  1618. centerImage();
  1619. }
  1620. else {
  1621. // Initialize image to be 0 size so export will still function
  1622. // if there's no cached sizes.
  1623. obj.attr({ width: 0, height: 0 });
  1624. // Create a dummy JavaScript image to get the width and height.
  1625. createElement('img', {
  1626. onload: function () {
  1627. var chart = charts[ren.chartIndex];
  1628. // Special case for SVGs on IE11, the width is not
  1629. // accessible until the image is part of the DOM
  1630. // (#2854).
  1631. if (this.width === 0) {
  1632. css(this, {
  1633. position: 'absolute',
  1634. top: '-999em'
  1635. });
  1636. doc.body.appendChild(this);
  1637. }
  1638. // Center the image
  1639. symbolSizes[imageSrc] = {
  1640. width: this.width,
  1641. height: this.height
  1642. };
  1643. obj.imgwidth = this.width;
  1644. obj.imgheight = this.height;
  1645. if (obj.element) {
  1646. centerImage();
  1647. }
  1648. // Clean up after #2854 workaround.
  1649. if (this.parentNode) {
  1650. this.parentNode.removeChild(this);
  1651. }
  1652. // Fire the load event when all external images are
  1653. // loaded
  1654. ren.imgCount--;
  1655. if (!ren.imgCount && chart && !chart.hasLoaded) {
  1656. chart.onload();
  1657. }
  1658. },
  1659. src: imageSrc
  1660. });
  1661. this.imgCount++;
  1662. }
  1663. }
  1664. return obj;
  1665. };
  1666. /**
  1667. * Define a clipping rectangle. The clipping rectangle is later applied
  1668. * to {@link SVGElement} objects through the {@link SVGElement#clip}
  1669. * function.
  1670. *
  1671. * @example
  1672. * var circle = renderer.circle(100, 100, 100)
  1673. * .attr({ fill: 'red' })
  1674. * .add();
  1675. * var clipRect = renderer.clipRect(100, 100, 100, 100);
  1676. *
  1677. * // Leave only the lower right quarter visible
  1678. * circle.clip(clipRect);
  1679. *
  1680. * @function Highcharts.SVGRenderer#clipRect
  1681. *
  1682. * @param {number} [x]
  1683. *
  1684. * @param {number} [y]
  1685. *
  1686. * @param {number} [width]
  1687. *
  1688. * @param {number} [height]
  1689. *
  1690. * @return {Highcharts.ClipRectElement}
  1691. * A clipping rectangle.
  1692. */
  1693. SVGRenderer.prototype.clipRect = function (x, y, width, height) {
  1694. var wrapper,
  1695. // Add a hyphen at the end to avoid confusion in testing indexes
  1696. // -1 and -10, -11 etc (#6550)
  1697. id = uniqueKey() + '-', clipPath = this.createElement('clipPath').attr({
  1698. id: id
  1699. }).add(this.defs);
  1700. wrapper = this.rect(x, y, width, height, 0).add(clipPath);
  1701. wrapper.id = id;
  1702. wrapper.clipPath = clipPath;
  1703. wrapper.count = 0;
  1704. return wrapper;
  1705. };
  1706. /**
  1707. * Draw text. The text can contain a subset of HTML, like spans and anchors
  1708. * and some basic text styling of these. For more advanced features like
  1709. * border and background, use {@link Highcharts.SVGRenderer#label} instead.
  1710. * To update the text after render, run `text.attr({ text: 'New text' })`.
  1711. *
  1712. * @sample highcharts/members/renderer-text-on-chart/
  1713. * Annotate the chart freely
  1714. * @sample highcharts/members/renderer-on-chart/
  1715. * Annotate with a border and in response to the data
  1716. * @sample highcharts/members/renderer-text/
  1717. * Formatted text
  1718. *
  1719. * @function Highcharts.SVGRenderer#text
  1720. *
  1721. * @param {string} [str]
  1722. * The text of (subset) HTML to draw.
  1723. *
  1724. * @param {number} [x]
  1725. * The x position of the text's lower left corner.
  1726. *
  1727. * @param {number} [y]
  1728. * The y position of the text's lower left corner.
  1729. *
  1730. * @param {boolean} [useHTML=false]
  1731. * Use HTML to render the text.
  1732. *
  1733. * @return {Highcharts.SVGElement}
  1734. * The text object.
  1735. */
  1736. SVGRenderer.prototype.text = function (str, x, y, useHTML) {
  1737. // declare variables
  1738. var renderer = this, wrapper, attribs = {};
  1739. if (useHTML && (renderer.allowHTML || !renderer.forExport)) {
  1740. return renderer.html(str, x, y);
  1741. }
  1742. attribs.x = Math.round(x || 0); // X always needed for line-wrap logic
  1743. if (y) {
  1744. attribs.y = Math.round(y);
  1745. }
  1746. if (defined(str)) {
  1747. attribs.text = str;
  1748. }
  1749. wrapper = renderer.createElement('text')
  1750. .attr(attribs);
  1751. if (!useHTML) {
  1752. wrapper.xSetter = function (value, key, element) {
  1753. var tspans = element.getElementsByTagName('tspan'), tspan, parentVal = element.getAttribute(key), i;
  1754. for (i = 0; i < tspans.length; i++) {
  1755. tspan = tspans[i];
  1756. // If the x values are equal, the tspan represents a
  1757. // linebreak
  1758. if (tspan.getAttribute(key) === parentVal) {
  1759. tspan.setAttribute(key, value);
  1760. }
  1761. }
  1762. element.setAttribute(key, value);
  1763. };
  1764. }
  1765. return wrapper;
  1766. };
  1767. /**
  1768. * Utility to return the baseline offset and total line height from the font
  1769. * size.
  1770. *
  1771. * @function Highcharts.SVGRenderer#fontMetrics
  1772. *
  1773. * @param {number|string} [fontSize]
  1774. * The current font size to inspect. If not given, the font size
  1775. * will be found from the DOM element.
  1776. *
  1777. * @param {Highcharts.SVGElement|Highcharts.SVGDOMElement} [elem]
  1778. * The element to inspect for a current font size.
  1779. *
  1780. * @return {Highcharts.FontMetricsObject}
  1781. * The font metrics.
  1782. */
  1783. SVGRenderer.prototype.fontMetrics = function (fontSize, elem) {
  1784. var lineHeight, baseline;
  1785. if ((this.styledMode || !/px/.test(fontSize)) &&
  1786. win.getComputedStyle // old IE doesn't support it
  1787. ) {
  1788. fontSize = elem && SVGElement.prototype.getStyle.call(elem, 'font-size');
  1789. }
  1790. else {
  1791. fontSize = fontSize ||
  1792. // When the elem is a DOM element (#5932)
  1793. (elem && elem.style && elem.style.fontSize) ||
  1794. // Fall back on the renderer style default
  1795. (this.style && this.style.fontSize);
  1796. }
  1797. // Handle different units
  1798. if (/px/.test(fontSize)) {
  1799. fontSize = pInt(fontSize);
  1800. }
  1801. else {
  1802. fontSize = 12;
  1803. }
  1804. // Empirical values found by comparing font size and bounding box
  1805. // height. Applies to the default font family.
  1806. // https://jsfiddle.net/highcharts/7xvn7/
  1807. lineHeight = fontSize < 24 ? fontSize + 3 : Math.round(fontSize * 1.2);
  1808. baseline = Math.round(lineHeight * 0.8);
  1809. return {
  1810. h: lineHeight,
  1811. b: baseline,
  1812. f: fontSize
  1813. };
  1814. };
  1815. /**
  1816. * Correct X and Y positioning of a label for rotation (#1764).
  1817. *
  1818. * @private
  1819. * @function Highcharts.SVGRenderer#rotCorr
  1820. *
  1821. * @param {number} baseline
  1822. *
  1823. * @param {number} rotation
  1824. *
  1825. * @param {boolean} [alterY]
  1826. *
  1827. * @param {Highcharts.PositionObject}
  1828. */
  1829. SVGRenderer.prototype.rotCorr = function (baseline, rotation, alterY) {
  1830. var y = baseline;
  1831. if (rotation && alterY) {
  1832. y = Math.max(y * Math.cos(rotation * deg2rad), 4);
  1833. }
  1834. return {
  1835. x: (-baseline / 3) * Math.sin(rotation * deg2rad),
  1836. y: y
  1837. };
  1838. };
  1839. /**
  1840. * Compatibility function to convert the legacy one-dimensional path array
  1841. * into an array of segments.
  1842. *
  1843. * It is used in maps to parse the `path` option, and in SVGRenderer.dSetter
  1844. * to support legacy paths from demos.
  1845. *
  1846. * @private
  1847. * @function Highcharts.SVGRenderer#pathToSegments
  1848. */
  1849. SVGRenderer.prototype.pathToSegments = function (path) {
  1850. var ret = [];
  1851. var segment = [];
  1852. var commandLength = {
  1853. A: 8,
  1854. C: 7,
  1855. H: 2,
  1856. L: 3,
  1857. M: 3,
  1858. Q: 5,
  1859. S: 5,
  1860. T: 3,
  1861. V: 2
  1862. };
  1863. // Short, non-typesafe parsing of the one-dimensional array. It splits
  1864. // the path on any string. This is not type checked against the tuple
  1865. // types, but is shorter, and doesn't require specific checks for any
  1866. // command type in SVG.
  1867. for (var i = 0; i < path.length; i++) {
  1868. // Command skipped, repeat previous or insert L/l for M/m
  1869. if (isString(segment[0]) &&
  1870. isNumber(path[i]) &&
  1871. segment.length === commandLength[(segment[0].toUpperCase())]) {
  1872. path.splice(i, 0, segment[0].replace('M', 'L').replace('m', 'l'));
  1873. }
  1874. // Split on string
  1875. if (typeof path[i] === 'string') {
  1876. if (segment.length) {
  1877. ret.push(segment.slice(0));
  1878. }
  1879. segment.length = 0;
  1880. }
  1881. segment.push(path[i]);
  1882. }
  1883. ret.push(segment.slice(0));
  1884. return ret;
  1885. /*
  1886. // Fully type-safe version where each tuple type is checked. The
  1887. // downside is filesize and a lack of flexibility for unsupported
  1888. // commands
  1889. const ret: SVGPath = [],
  1890. commands = {
  1891. A: 7,
  1892. C: 6,
  1893. H: 1,
  1894. L: 2,
  1895. M: 2,
  1896. Q: 4,
  1897. S: 4,
  1898. T: 2,
  1899. V: 1,
  1900. Z: 0
  1901. };
  1902. let i = 0,
  1903. lastI = 0,
  1904. lastCommand;
  1905. while (i < path.length) {
  1906. const item = path[i];
  1907. let command;
  1908. if (typeof item === 'string') {
  1909. command = item;
  1910. i += 1;
  1911. } else {
  1912. command = lastCommand || 'M';
  1913. }
  1914. // Upper case
  1915. const commandUC = command.toUpperCase();
  1916. if (commandUC in commands) {
  1917. // No numeric parameters
  1918. if (command === 'Z' || command === 'z') {
  1919. ret.push([command]);
  1920. // One numeric parameter
  1921. } else {
  1922. const val0 = path[i];
  1923. if (typeof val0 === 'number') {
  1924. // Horizontal line to
  1925. if (command === 'H' || command === 'h') {
  1926. ret.push([command, val0]);
  1927. i += 1;
  1928. // Vertical line to
  1929. } else if (command === 'V' || command === 'v') {
  1930. ret.push([command, val0]);
  1931. i += 1;
  1932. // Two numeric parameters
  1933. } else {
  1934. const val1 = path[i + 1];
  1935. if (typeof val1 === 'number') {
  1936. // lineTo
  1937. if (command === 'L' || command === 'l') {
  1938. ret.push([command, val0, val1]);
  1939. i += 2;
  1940. // moveTo
  1941. } else if (command === 'M' || command === 'm') {
  1942. ret.push([command, val0, val1]);
  1943. i += 2;
  1944. // Smooth quadratic bezier
  1945. } else if (command === 'T' || command === 't') {
  1946. ret.push([command, val0, val1]);
  1947. i += 2;
  1948. // Four numeric parameters
  1949. } else {
  1950. const val2 = path[i + 2],
  1951. val3 = path[i + 3];
  1952. if (
  1953. typeof val2 === 'number' &&
  1954. typeof val3 === 'number'
  1955. ) {
  1956. // Quadratic bezier to
  1957. if (
  1958. command === 'Q' ||
  1959. command === 'q'
  1960. ) {
  1961. ret.push([
  1962. command,
  1963. val0,
  1964. val1,
  1965. val2,
  1966. val3
  1967. ]);
  1968. i += 4;
  1969. // Smooth cubic bezier to
  1970. } else if (
  1971. command === 'S' ||
  1972. command === 's'
  1973. ) {
  1974. ret.push([
  1975. command,
  1976. val0,
  1977. val1,
  1978. val2,
  1979. val3
  1980. ]);
  1981. i += 4;
  1982. // Six numeric parameters
  1983. } else {
  1984. const val4 = path[i + 4],
  1985. val5 = path[i + 5];
  1986. if (
  1987. typeof val4 === 'number' &&
  1988. typeof val5 === 'number'
  1989. ) {
  1990. // Curve to
  1991. if (
  1992. command === 'C' ||
  1993. command === 'c'
  1994. ) {
  1995. ret.push([
  1996. command,
  1997. val0,
  1998. val1,
  1999. val2,
  2000. val3,
  2001. val4,
  2002. val5
  2003. ]);
  2004. i += 6;
  2005. // Seven numeric parameters
  2006. } else {
  2007. const val6 = path[i + 6];
  2008. // Arc to
  2009. if (
  2010. typeof val6 ===
  2011. 'number' &&
  2012. (
  2013. command === 'A' ||
  2014. command === 'a'
  2015. )
  2016. ) {
  2017. ret.push([
  2018. command,
  2019. val0,
  2020. val1,
  2021. val2,
  2022. val3,
  2023. val4,
  2024. val5,
  2025. val6
  2026. ]);
  2027. i += 7;
  2028. }
  2029. }
  2030. }
  2031. }
  2032. }
  2033. }
  2034. }
  2035. }
  2036. }
  2037. }
  2038. }
  2039. // An unmarked command following a moveTo is a lineTo
  2040. lastCommand = command === 'M' ? 'L' : command;
  2041. if (i === lastI) {
  2042. break;
  2043. }
  2044. lastI = i;
  2045. }
  2046. return ret;
  2047. */
  2048. };
  2049. /**
  2050. * Draw a label, which is an extended text element with support for border
  2051. * and background. Highcharts creates a `g` element with a text and a `path`
  2052. * or `rect` inside, to make it behave somewhat like a HTML div. Border and
  2053. * background are set through `stroke`, `stroke-width` and `fill` attributes
  2054. * using the {@link Highcharts.SVGElement#attr|attr} method. To update the
  2055. * text after render, run `label.attr({ text: 'New text' })`.
  2056. *
  2057. * @sample highcharts/members/renderer-label-on-chart/
  2058. * A label on the chart
  2059. *
  2060. * @function Highcharts.SVGRenderer#label
  2061. *
  2062. * @param {string} str
  2063. * The initial text string or (subset) HTML to render.
  2064. *
  2065. * @param {number} x
  2066. * The x position of the label's left side.
  2067. *
  2068. * @param {number} [y]
  2069. * The y position of the label's top side or baseline, depending on
  2070. * the `baseline` parameter.
  2071. *
  2072. * @param {string} [shape='rect']
  2073. * The shape of the label's border/background, if any. Defaults to
  2074. * `rect`. Other possible values are `callout` or other shapes
  2075. * defined in {@link Highcharts.SVGRenderer#symbols}.
  2076. *
  2077. * @param {number} [anchorX]
  2078. * In case the `shape` has a pointer, like a flag, this is the
  2079. * coordinates it should be pinned to.
  2080. *
  2081. * @param {number} [anchorY]
  2082. * In case the `shape` has a pointer, like a flag, this is the
  2083. * coordinates it should be pinned to.
  2084. *
  2085. * @param {boolean} [useHTML=false]
  2086. * Wether to use HTML to render the label.
  2087. *
  2088. * @param {boolean} [baseline=false]
  2089. * Whether to position the label relative to the text baseline,
  2090. * like {@link Highcharts.SVGRenderer#text|renderer.text}, or to the
  2091. * upper border of the rectangle.
  2092. *
  2093. * @param {string} [className]
  2094. * Class name for the group.
  2095. *
  2096. * @return {Highcharts.SVGElement}
  2097. * The generated label.
  2098. */
  2099. SVGRenderer.prototype.label = function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {
  2100. return new SVGLabel(this, str, x, y, shape, anchorX, anchorY, useHTML, baseline, className);
  2101. };
  2102. return SVGRenderer;
  2103. }());
  2104. /**
  2105. * A pointer to the renderer's associated Element class. The VMLRenderer
  2106. * will have a pointer to VMLElement here.
  2107. *
  2108. * @name Highcharts.SVGRenderer#Element
  2109. * @type {Highcharts.SVGElement}
  2110. */
  2111. SVGRenderer.prototype.Element = SVGElement;
  2112. /**
  2113. * @private
  2114. */
  2115. SVGRenderer.prototype.SVG_NS = SVG_NS;
  2116. /**
  2117. * Dummy function for plugins, called every time the renderer is updated.
  2118. * Prior to Highcharts 5, this was used for the canvg renderer.
  2119. *
  2120. * @deprecated
  2121. * @function Highcharts.SVGRenderer#draw
  2122. */
  2123. SVGRenderer.prototype.draw = noop;
  2124. /**
  2125. * A collection of characters mapped to HTML entities. When `useHTML` on an
  2126. * element is true, these entities will be rendered correctly by HTML. In
  2127. * the SVG pseudo-HTML, they need to be unescaped back to simple characters,
  2128. * so for example `&lt;` will render as `<`.
  2129. *
  2130. * @example
  2131. * // Add support for unescaping quotes
  2132. * Highcharts.SVGRenderer.prototype.escapes['"'] = '&quot;';
  2133. *
  2134. * @name Highcharts.SVGRenderer#escapes
  2135. * @type {Highcharts.Dictionary<string>}
  2136. */
  2137. SVGRenderer.prototype.escapes = {
  2138. '&': '&amp;',
  2139. '<': '&lt;',
  2140. '>': '&gt;',
  2141. "'": '&#39;',
  2142. '"': '&quot;'
  2143. };
  2144. /**
  2145. * An extendable collection of functions for defining symbol paths.
  2146. *
  2147. * @name Highcharts.SVGRenderer#symbols
  2148. * @type {Highcharts.SymbolDictionary}
  2149. */
  2150. SVGRenderer.prototype.symbols = {
  2151. circle: function (x, y, w, h) {
  2152. // Return a full arc
  2153. return this.arc(x + w / 2, y + h / 2, w / 2, h / 2, {
  2154. start: Math.PI * 0.5,
  2155. end: Math.PI * 2.5,
  2156. open: false
  2157. });
  2158. },
  2159. square: function (x, y, w, h) {
  2160. return [
  2161. ['M', x, y],
  2162. ['L', x + w, y],
  2163. ['L', x + w, y + h],
  2164. ['L', x, y + h],
  2165. ['Z']
  2166. ];
  2167. },
  2168. triangle: function (x, y, w, h) {
  2169. return [
  2170. ['M', x + w / 2, y],
  2171. ['L', x + w, y + h],
  2172. ['L', x, y + h],
  2173. ['Z']
  2174. ];
  2175. },
  2176. 'triangle-down': function (x, y, w, h) {
  2177. return [
  2178. ['M', x, y],
  2179. ['L', x + w, y],
  2180. ['L', x + w / 2, y + h],
  2181. ['Z']
  2182. ];
  2183. },
  2184. diamond: function (x, y, w, h) {
  2185. return [
  2186. ['M', x + w / 2, y],
  2187. ['L', x + w, y + h / 2],
  2188. ['L', x + w / 2, y + h],
  2189. ['L', x, y + h / 2],
  2190. ['Z']
  2191. ];
  2192. },
  2193. arc: function (x, y, w, h, options) {
  2194. var arc = [];
  2195. if (options) {
  2196. var start = options.start || 0, end = options.end || 0, rx = options.r || w, ry = options.r || h || w, proximity = 0.001, fullCircle = Math.abs(end - start - 2 * Math.PI) <
  2197. proximity,
  2198. // Substract a small number to prevent cos and sin of start and
  2199. // end from becoming equal on 360 arcs (related: #1561)
  2200. end = 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),
  2201. // Proximity takes care of rounding errors around PI (#6971)
  2202. longArc = pick(options.longArc, end - start - Math.PI < proximity ? 0 : 1);
  2203. arc.push([
  2204. 'M',
  2205. x + rx * cosStart,
  2206. y + ry * sinStart
  2207. ], [
  2208. 'A',
  2209. rx,
  2210. ry,
  2211. 0,
  2212. longArc,
  2213. pick(options.clockwise, 1),
  2214. x + rx * cosEnd,
  2215. y + ry * sinEnd
  2216. ]);
  2217. if (defined(innerRadius)) {
  2218. arc.push(open ?
  2219. [
  2220. 'M',
  2221. x + innerRadius * cosEnd,
  2222. y + innerRadius * sinEnd
  2223. ] : [
  2224. 'L',
  2225. x + innerRadius * cosEnd,
  2226. y + innerRadius * sinEnd
  2227. ], [
  2228. 'A',
  2229. innerRadius,
  2230. innerRadius,
  2231. 0,
  2232. longArc,
  2233. // Clockwise - opposite to the outer arc clockwise
  2234. defined(options.clockwise) ? 1 - options.clockwise : 0,
  2235. x + innerRadius * cosStart,
  2236. y + innerRadius * sinStart
  2237. ]);
  2238. }
  2239. if (!open) {
  2240. arc.push(['Z']);
  2241. }
  2242. }
  2243. return arc;
  2244. },
  2245. /**
  2246. * Callout shape used for default tooltips, also used for rounded
  2247. * rectangles in VML
  2248. */
  2249. callout: function (x, y, w, h, options) {
  2250. var arrowLength = 6, halfDistance = 6, r = Math.min((options && options.r) || 0, w, h), safeDistance = r + halfDistance, anchorX = options && options.anchorX || 0, anchorY = options && options.anchorY || 0, path;
  2251. path = [
  2252. ['M', x + r, y],
  2253. ['L', x + w - r, y],
  2254. ['C', x + w, y, x + w, y, x + w, y + r],
  2255. ['L', x + w, y + h - r],
  2256. ['C', x + w, y + h, x + w, y + h, x + w - r, y + h],
  2257. ['L', x + r, y + h],
  2258. ['C', x, y + h, x, y + h, x, y + h - r],
  2259. ['L', x, y + r],
  2260. ['C', x, y, x, y, x + r, y] // top-left corner
  2261. ];
  2262. // Anchor on right side
  2263. if (anchorX && anchorX > w) {
  2264. // Chevron
  2265. if (anchorY > y + safeDistance &&
  2266. anchorY < y + h - safeDistance) {
  2267. path.splice(3, 1, ['L', x + w, anchorY - halfDistance], ['L', x + w + arrowLength, anchorY], ['L', x + w, anchorY + halfDistance], ['L', x + w, y + h - r]);
  2268. // Simple connector
  2269. }
  2270. else {
  2271. path.splice(3, 1, ['L', x + w, h / 2], ['L', anchorX, anchorY], ['L', x + w, h / 2], ['L', x + w, y + h - r]);
  2272. }
  2273. // Anchor on left side
  2274. }
  2275. else if (anchorX && anchorX < 0) {
  2276. // Chevron
  2277. if (anchorY > y + safeDistance &&
  2278. anchorY < y + h - safeDistance) {
  2279. path.splice(7, 1, ['L', x, anchorY + halfDistance], ['L', x - arrowLength, anchorY], ['L', x, anchorY - halfDistance], ['L', x, y + r]);
  2280. // Simple connector
  2281. }
  2282. else {
  2283. path.splice(7, 1, ['L', x, h / 2], ['L', anchorX, anchorY], ['L', x, h / 2], ['L', x, y + r]);
  2284. }
  2285. }
  2286. else if ( // replace bottom
  2287. anchorY &&
  2288. anchorY > h &&
  2289. anchorX > x + safeDistance &&
  2290. anchorX < x + w - safeDistance) {
  2291. path.splice(5, 1, ['L', anchorX + halfDistance, y + h], ['L', anchorX, y + h + arrowLength], ['L', anchorX - halfDistance, y + h], ['L', x + r, y + h]);
  2292. }
  2293. else if ( // replace top
  2294. anchorY &&
  2295. anchorY < 0 &&
  2296. anchorX > x + safeDistance &&
  2297. anchorX < x + w - safeDistance) {
  2298. path.splice(1, 1, ['L', anchorX - halfDistance, y], ['L', anchorX, y - arrowLength], ['L', anchorX + halfDistance, y], ['L', w - r, y]);
  2299. }
  2300. return path;
  2301. }
  2302. };
  2303. H.SVGRenderer = SVGRenderer;
  2304. H.Renderer = H.SVGRenderer;
  2305. export default H.Renderer;