Oldie.js 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244
  1. /* *
  2. *
  3. * (c) 2010-2021 Torstein Honsi
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * Support for old IE browsers (6, 7 and 8) in Highcharts v6+.
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. 'use strict';
  13. import Chart from '../../Core/Chart/Chart.js';
  14. import Color from '../../Core/Color/Color.js';
  15. var color = Color.parse;
  16. import H from '../../Core/Globals.js';
  17. var deg2rad = H.deg2rad, doc = H.doc, noop = H.noop, svg = H.svg, win = H.win;
  18. import palette from '../../Core/Color/Palette.js';
  19. import Pointer from '../../Core/Pointer.js';
  20. import SVGElement from '../../Core/Renderer/SVG/SVGElement.js';
  21. import SVGRenderer from '../../Core/Renderer/SVG/SVGRenderer3D.js';
  22. import U from '../../Core/Utilities.js';
  23. var addEvent = U.addEvent, createElement = U.createElement, css = U.css, defined = U.defined, discardElement = U.discardElement, erase = U.erase, extend = U.extend, extendClass = U.extendClass, getOptions = U.getOptions, isArray = U.isArray, isNumber = U.isNumber, isObject = U.isObject, merge = U.merge, offset = U.offset, pick = U.pick, pInt = U.pInt, setOptions = U.setOptions, uniqueKey = U.uniqueKey;
  24. import VMLRenderer3D from './VMLRenderer3D.js';
  25. var VMLRenderer, VMLElement;
  26. /**
  27. * Path to the pattern image required by VML browsers in order to
  28. * draw radial gradients.
  29. *
  30. * @type {string}
  31. * @default http://code.highcharts.com/{version}/gfx/vml-radial-gradient.png
  32. * @since 2.3.0
  33. * @requires modules/oldie
  34. * @apioption global.VMLRadialGradientURL
  35. */
  36. getOptions().global.VMLRadialGradientURL =
  37. 'http://code.highcharts.com/9.0.1/gfx/vml-radial-gradient.png';
  38. // Utilites
  39. if (doc && !doc.defaultView) {
  40. H.getStyle = U.getStyle = function (el, prop) {
  41. var val, alias = {
  42. width: 'clientWidth',
  43. height: 'clientHeight'
  44. }[prop];
  45. if (el.style[prop]) {
  46. return pInt(el.style[prop]);
  47. }
  48. if (prop === 'opacity') {
  49. prop = 'filter';
  50. }
  51. // Getting the rendered width and height
  52. if (alias) {
  53. el.style.zoom = 1;
  54. return Math.max(el[alias] - 2 * U.getStyle(el, 'padding'), 0);
  55. }
  56. val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b) {
  57. return b.toUpperCase();
  58. })];
  59. if (prop === 'filter') {
  60. val = val.replace(/alpha\(opacity=([0-9]+)\)/, function (a, b) {
  61. return (b / 100);
  62. });
  63. }
  64. return val === '' ? 1 : pInt(val);
  65. };
  66. }
  67. /* eslint-disable no-invalid-this, valid-jsdoc */
  68. if (!svg) {
  69. // Prevent wrapping from creating false offsetWidths in export in legacy IE.
  70. // This applies only to charts for export, where IE runs the SVGRenderer
  71. // instead of the VMLRenderer
  72. // (#1079, #1063)
  73. addEvent(SVGElement, 'afterInit', function () {
  74. if (this.element.nodeName === 'text') {
  75. this.css({
  76. position: 'absolute'
  77. });
  78. }
  79. });
  80. /**
  81. * Old IE override for pointer normalize, adds chartX and chartY to event
  82. * arguments.
  83. *
  84. * @ignore
  85. * @function Highcharts.Pointer#normalize
  86. * @param {global.PointerEvent} e
  87. * @param {boolean} [chartPosition=false]
  88. * @return {Highcharts.PointerEventObject}
  89. */
  90. Pointer.prototype.normalize = function (e, chartPosition) {
  91. e = e || win.event;
  92. if (!e.target) {
  93. e.target = e.srcElement;
  94. }
  95. // Get mouse position
  96. if (!chartPosition) {
  97. this.chartPosition = chartPosition = this.getChartPosition();
  98. }
  99. return extend(e, {
  100. // #2005, #2129: the second case is for IE10 quirks mode within
  101. // framesets
  102. chartX: Math.round(Math.max(e.x, e.clientX - chartPosition.left)),
  103. chartY: Math.round(e.y)
  104. });
  105. };
  106. /**
  107. * Further sanitize the mock-SVG that is generated when exporting charts in
  108. * oldIE.
  109. *
  110. * @private
  111. * @function Highcharts.Chart#ieSanitizeSVG
  112. */
  113. Chart.prototype.ieSanitizeSVG = function (svg) {
  114. svg = svg
  115. .replace(/<IMG /g, '<image ')
  116. .replace(/<(\/?)TITLE>/g, '<$1title>')
  117. .replace(/height=([^" ]+)/g, 'height="$1"')
  118. .replace(/width=([^" ]+)/g, 'width="$1"')
  119. .replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>')
  120. .replace(/ id=([^" >]+)/g, ' id="$1"') // #4003
  121. .replace(/class=([^" >]+)/g, 'class="$1"')
  122. .replace(/ transform /g, ' ')
  123. .replace(/:(path|rect)/g, '$1')
  124. .replace(/style="([^"]+)"/g, function (s) {
  125. return s.toLowerCase();
  126. });
  127. return svg;
  128. };
  129. /**
  130. * VML namespaces can't be added until after complete. Listening
  131. * for Perini's doScroll hack is not enough.
  132. *
  133. * @private
  134. * @function Highcharts.Chart#isReadyToRender
  135. */
  136. Chart.prototype.isReadyToRender = function () {
  137. var chart = this;
  138. // Note: win == win.top is required
  139. if (!svg &&
  140. (win == win.top && // eslint-disable-line eqeqeq
  141. doc.readyState !== 'complete')) {
  142. doc.attachEvent('onreadystatechange', function () {
  143. doc.detachEvent('onreadystatechange', chart.firstRender);
  144. if (doc.readyState === 'complete') {
  145. chart.firstRender();
  146. }
  147. });
  148. return false;
  149. }
  150. return true;
  151. };
  152. // IE compatibility hack for generating SVG content that it doesn't really
  153. // understand. Used by the exporting module.
  154. if (!doc.createElementNS) {
  155. doc.createElementNS = function (ns, tagName) {
  156. return doc.createElement(tagName);
  157. };
  158. }
  159. /**
  160. * Old IE polyfill for addEventListener, called from inside the addEvent
  161. * function.
  162. *
  163. * @private
  164. * @function Highcharts.addEventListenerPolyfill<T>
  165. * @param {string} type
  166. * @param {Highcharts.EventCallbackFunction<T>} fn
  167. * @return {void}
  168. */
  169. H.addEventListenerPolyfill = function (type, fn) {
  170. var el = this;
  171. /**
  172. * @private
  173. */
  174. function wrappedFn(e) {
  175. e.target = e.srcElement || win; // #2820
  176. fn.call(el, e);
  177. }
  178. if (el.attachEvent) {
  179. if (!el.hcEventsIE) {
  180. el.hcEventsIE = {};
  181. }
  182. // unique function string (#6746)
  183. if (!fn.hcKey) {
  184. fn.hcKey = uniqueKey();
  185. }
  186. // Link wrapped fn with original fn, so we can get this in
  187. // removeEvent
  188. el.hcEventsIE[fn.hcKey] = wrappedFn;
  189. el.attachEvent('on' + type, wrappedFn);
  190. }
  191. };
  192. /**
  193. * @private
  194. * @function Highcharts.removeEventListenerPolyfill<T>
  195. * @param {string} type
  196. * @param {Highcharts.EventCallbackFunction<T>} fn
  197. * @return {void}
  198. */
  199. H.removeEventListenerPolyfill = function (type, fn) {
  200. if (this.detachEvent) {
  201. fn = this.hcEventsIE[fn.hcKey];
  202. this.detachEvent('on' + type, fn);
  203. }
  204. };
  205. /**
  206. * The VML element wrapper.
  207. *
  208. * @private
  209. * @class
  210. * @name Highcharts.VMLElement
  211. *
  212. * @augments Highcharts.SVGElement
  213. */
  214. VMLElement = {
  215. docMode8: doc && doc.documentMode === 8,
  216. /**
  217. * Initialize a new VML element wrapper. It builds the markup as a
  218. * string to minimize DOM traffic.
  219. *
  220. * @function Highcharts.VMLElement#init
  221. * @param {Highcharts.VMLRenderer} renderer
  222. * @param {string} nodeName
  223. */
  224. init: function (renderer, nodeName) {
  225. var wrapper = this, markup = ['<', nodeName, ' filled="f" stroked="f"'], style = ['position: ', 'absolute', ';'], isDiv = nodeName === 'div';
  226. // divs and shapes need size
  227. if (nodeName === 'shape' || isDiv) {
  228. style.push('left:0;top:0;width:1px;height:1px;');
  229. }
  230. style.push('visibility: ', isDiv ? 'hidden' : 'visible');
  231. markup.push(' style="', style.join(''), '"/>');
  232. // create element with default attributes and style
  233. if (nodeName) {
  234. markup = isDiv || nodeName === 'span' || nodeName === 'img' ?
  235. markup.join('') :
  236. renderer.prepVML(markup);
  237. wrapper.element = createElement(markup);
  238. }
  239. wrapper.renderer = renderer;
  240. },
  241. /**
  242. * Add the node to the given parent
  243. *
  244. * @function Highcharts.VMLElement
  245. * @param {Highcharts.VMLElement} parent
  246. * @return {Highcharts.VMLElement}
  247. */
  248. add: function (parent) {
  249. var wrapper = this, renderer = wrapper.renderer, element = wrapper.element, box = renderer.box, inverted = parent && parent.inverted,
  250. // get the parent node
  251. parentNode = parent ?
  252. parent.element || parent :
  253. box;
  254. if (parent) {
  255. this.parentGroup = parent;
  256. }
  257. // if the parent group is inverted, apply inversion on all children
  258. if (inverted) { // only on groups
  259. renderer.invertChild(element, parentNode);
  260. }
  261. // append it
  262. parentNode.appendChild(element);
  263. // align text after adding to be able to read offset
  264. wrapper.added = true;
  265. if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {
  266. wrapper.updateTransform();
  267. }
  268. // fire an event for internal hooks
  269. if (wrapper.onAdd) {
  270. wrapper.onAdd();
  271. }
  272. // IE8 Standards can't set the class name before the element is
  273. // appended
  274. if (this.className) {
  275. this.attr('class', this.className);
  276. }
  277. return wrapper;
  278. },
  279. /**
  280. * VML always uses htmlUpdateTransform
  281. *
  282. * @function Highcharts.VMLElement#updateTransform
  283. */
  284. updateTransform: SVGElement.prototype.htmlUpdateTransform,
  285. /**
  286. * Set the rotation of a span with oldIE's filter
  287. *
  288. * @function Highcharts.VMLElement#setSpanRotation
  289. * @return {void}
  290. */
  291. setSpanRotation: function () {
  292. // Adjust for alignment and rotation. Rotation of useHTML content is
  293. // not yet implemented but it can probably be implemented for
  294. // Firefox 3.5+ on user request. FF3.5+ has support for CSS3
  295. // transform. The getBBox method also needs to be updated to
  296. // compensate for the rotation, like it currently does for SVG.
  297. // Test case: https://jsfiddle.net/highcharts/Ybt44/
  298. var rotation = this.rotation, costheta = Math.cos(rotation * deg2rad), sintheta = Math.sin(rotation * deg2rad);
  299. css(this.element, {
  300. filter: rotation ? [
  301. 'progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
  302. ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
  303. ', sizingMethod=\'auto expand\')'
  304. ].join('') : 'none'
  305. });
  306. },
  307. /**
  308. * Get the positioning correction for the span after rotating.
  309. *
  310. * @function Highcharts.VMLElement#getSpanCorrection
  311. */
  312. getSpanCorrection: function (width, baseline, alignCorrection, rotation, align) {
  313. var costheta = rotation ? Math.cos(rotation * deg2rad) : 1, sintheta = rotation ? Math.sin(rotation * deg2rad) : 0, height = pick(this.elemHeight, this.element.offsetHeight), quad, nonLeft = align && align !== 'left';
  314. // correct x and y
  315. this.xCorr = (costheta < 0 && -width);
  316. this.yCorr = (sintheta < 0 && -height);
  317. // correct for baseline and corners spilling out after rotation
  318. quad = costheta * sintheta < 0;
  319. this.xCorr += (sintheta *
  320. baseline *
  321. (quad ? 1 - alignCorrection : alignCorrection));
  322. this.yCorr -= (costheta *
  323. baseline *
  324. (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1));
  325. // correct for the length/height of the text
  326. if (nonLeft) {
  327. this.xCorr -=
  328. width * alignCorrection * (costheta < 0 ? -1 : 1);
  329. if (rotation) {
  330. this.yCorr -= (height *
  331. alignCorrection *
  332. (sintheta < 0 ? -1 : 1));
  333. }
  334. css(this.element, {
  335. textAlign: align
  336. });
  337. }
  338. },
  339. /**
  340. * Converts a subset of an SVG path definition to its VML counterpart.
  341. * Takes an array as the parameter and returns a string.
  342. *
  343. * @function Highcharts.VMLElement#pathToVML
  344. */
  345. pathToVML: function (value) {
  346. // convert paths
  347. var i = value.length, path = [];
  348. while (i--) {
  349. // Multiply by 10 to allow subpixel precision.
  350. // Substracting half a pixel seems to make the coordinates
  351. // align with SVG, but this hasn't been tested thoroughly
  352. if (isNumber(value[i])) {
  353. path[i] = Math.round(value[i] * 10) - 5;
  354. }
  355. else if (value[i] === 'Z') { // close the path
  356. path[i] = 'x';
  357. }
  358. else {
  359. path[i] = value[i];
  360. // When the start X and end X coordinates of an arc are too
  361. // close, they are rounded to the same value above. In this
  362. // case, substract or add 1 from the end X and Y positions.
  363. // #186, #760, #1371, #1410.
  364. if (value.isArc &&
  365. (value[i] === 'wa' || value[i] === 'at')) {
  366. // Start and end X
  367. if (path[i + 5] === path[i + 7]) {
  368. path[i + 7] +=
  369. value[i + 7] > value[i + 5] ? 1 : -1;
  370. }
  371. // Start and end Y
  372. if (path[i + 6] === path[i + 8]) {
  373. path[i + 8] +=
  374. value[i + 8] > value[i + 6] ? 1 : -1;
  375. }
  376. }
  377. }
  378. }
  379. return path.join(' ') || 'x';
  380. },
  381. /**
  382. * Set the element's clipping to a predefined rectangle
  383. *
  384. * @function Highcharts.VMLElement#clip
  385. * @param {Highcharts.VMLClipRectObject} clipRect
  386. * @return {Highcharts.VMLElement}
  387. */
  388. clip: function (clipRect) {
  389. var wrapper = this, clipMembers, cssRet;
  390. if (clipRect) {
  391. clipMembers = clipRect.members;
  392. // Ensure unique list of elements (#1258)
  393. erase(clipMembers, wrapper);
  394. clipMembers.push(wrapper);
  395. wrapper.destroyClip = function () {
  396. erase(clipMembers, wrapper);
  397. };
  398. cssRet = clipRect.getCSS(wrapper);
  399. }
  400. else {
  401. if (wrapper.destroyClip) {
  402. wrapper.destroyClip();
  403. }
  404. cssRet = {
  405. clip: wrapper.docMode8 ? 'inherit' : 'rect(auto)'
  406. }; // #1214
  407. }
  408. return wrapper.css(cssRet);
  409. },
  410. /**
  411. * Set styles for the element
  412. *
  413. * @function Highcharts.VMLElement#css
  414. * @param {Highcharts.CSSObject} styles
  415. * @return {Highcharts.VMLElement}
  416. */
  417. css: SVGElement.prototype.htmlCss,
  418. /**
  419. * Removes a child either by removeChild or move to garbageBin.
  420. * Issue 490; in VML removeChild results in Orphaned nodes according to
  421. * sIEve, discardElement does not.
  422. *
  423. * @function Highcharts.VMLElement#safeRemoveChild
  424. * @param {Highcharts.HTMLDOMElement} element
  425. * @return {void}
  426. */
  427. safeRemoveChild: function (element) {
  428. // discardElement will detach the node from its parent before
  429. // attaching it to the garbage bin. Therefore it is important that
  430. // the node is attached and have parent.
  431. if (element.parentNode) {
  432. discardElement(element);
  433. }
  434. },
  435. /**
  436. * Extend element.destroy by removing it from the clip members array
  437. *
  438. * @function Highcharts.VMLElement#destroy
  439. */
  440. destroy: function () {
  441. if (this.destroyClip) {
  442. this.destroyClip();
  443. }
  444. return SVGElement.prototype.destroy.apply(this);
  445. },
  446. /**
  447. * Add an event listener. VML override for normalizing event parameters.
  448. *
  449. * @function Highcharts.VMLElement#on
  450. * @param {string} eventType
  451. * @param {Function} handler
  452. * @return {Highcharts.VMLElement}
  453. */
  454. on: function (eventType, handler) {
  455. // simplest possible event model for internal use
  456. this.element['on' + eventType] = function () {
  457. var e = win.event;
  458. e.target = e.srcElement;
  459. handler(e);
  460. };
  461. return this;
  462. },
  463. /**
  464. * In stacked columns, cut off the shadows so that they don't overlap
  465. *
  466. * @function Highcharts.VMLElement#cutOffPath
  467. * @param {string} path
  468. * @param {number} length
  469. * @return {string}
  470. */
  471. cutOffPath: function (path, length) {
  472. var len;
  473. // The extra comma tricks the trailing comma remover in
  474. // "gulp scripts" task
  475. path = path.split(/[ ,]/);
  476. len = path.length;
  477. if (len === 9 || len === 11) {
  478. path[len - 4] = path[len - 2] =
  479. pInt(path[len - 2]) - 10 * length;
  480. }
  481. return path.join(' ');
  482. },
  483. /**
  484. * Apply a drop shadow by copying elements and giving them different
  485. * strokes.
  486. *
  487. * @function Highcharts.VMLElement#shadow
  488. * @param {Highcharts.ShadowOptionsObject} shadowOptions
  489. * @param {Highcharts.VMLElement} group
  490. * @param {boolean} cutOff
  491. * @return {Highcharts.VMLElement}
  492. */
  493. shadow: function (shadowOptions, group, cutOff) {
  494. var shadows = [], i, element = this.element, renderer = this.renderer, shadow, elemStyle = element.style, markup, path = element.path, strokeWidth, modifiedPath, shadowWidth, shadowElementOpacity;
  495. // some times empty paths are not strings
  496. if (path && typeof path.value !== 'string') {
  497. path = 'x';
  498. }
  499. modifiedPath = path;
  500. if (shadowOptions) {
  501. shadowWidth = pick(shadowOptions.width, 3);
  502. shadowElementOpacity =
  503. (shadowOptions.opacity || 0.15) / shadowWidth;
  504. for (i = 1; i <= 3; i++) {
  505. strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
  506. // Cut off shadows for stacked column items
  507. if (cutOff) {
  508. modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5);
  509. }
  510. markup = [
  511. '<shape isShadow="true" strokeweight="', strokeWidth,
  512. '" filled="false" path="', modifiedPath,
  513. '" coordsize="10 10" style="', element.style.cssText,
  514. '" />'
  515. ];
  516. shadow = createElement(renderer.prepVML(markup), null, {
  517. left: pInt(elemStyle.left) +
  518. pick(shadowOptions.offsetX, 1),
  519. top: pInt(elemStyle.top) +
  520. pick(shadowOptions.offsetY, 1)
  521. });
  522. if (cutOff) {
  523. shadow.cutOff = strokeWidth + 1;
  524. }
  525. // apply the opacity
  526. markup = [
  527. '<stroke color="',
  528. shadowOptions.color || palette.neutralColor100,
  529. '" opacity="', shadowElementOpacity * i, '"/>'
  530. ];
  531. createElement(renderer.prepVML(markup), null, null, shadow);
  532. // insert it
  533. if (group) {
  534. group.element.appendChild(shadow);
  535. }
  536. else {
  537. element.parentNode
  538. .insertBefore(shadow, element);
  539. }
  540. // record it
  541. shadows.push(shadow);
  542. }
  543. this.shadows = shadows;
  544. }
  545. return this;
  546. },
  547. updateShadows: noop,
  548. setAttr: function (key, value) {
  549. if (this.docMode8) { // IE8 setAttribute bug
  550. this.element[key] = value;
  551. }
  552. else {
  553. this.element.setAttribute(key, value);
  554. }
  555. },
  556. getAttr: function (key) {
  557. if (this.docMode8) { // IE8 setAttribute bug
  558. return this.element[key];
  559. }
  560. return this.element.getAttribute(key);
  561. },
  562. classSetter: function (value) {
  563. // IE8 Standards mode has problems retrieving the className unless
  564. // set like this. IE8 Standards can't set the class name before the
  565. // element is appended.
  566. (this.added ? this.element : this).className = value;
  567. },
  568. dashstyleSetter: function (value, key, element) {
  569. var strokeElem = element.getElementsByTagName('stroke')[0] ||
  570. createElement(this.renderer.prepVML(['<stroke/>']), null, null, element);
  571. strokeElem[key] = value || 'solid';
  572. // Because changing stroke-width will change the dash length and
  573. // cause an epileptic effect
  574. this[key] = value;
  575. },
  576. dSetter: function (value, key, element) {
  577. var i, shadows = this.shadows;
  578. value = value || [];
  579. // Used in getter for animation
  580. this.d = value.join && value.join(' ');
  581. element.path = value = this.pathToVML(value);
  582. // update shadows
  583. if (shadows) {
  584. i = shadows.length;
  585. while (i--) {
  586. shadows[i].path = shadows[i].cutOff ?
  587. this.cutOffPath(value, shadows[i].cutOff) :
  588. value;
  589. }
  590. }
  591. this.setAttr(key, value);
  592. },
  593. fillSetter: function (value, key, element) {
  594. var nodeName = element.nodeName;
  595. if (nodeName === 'SPAN') { // text color
  596. element.style.color = value;
  597. }
  598. else if (nodeName !== 'IMG') { // #1336
  599. element.filled = value !== 'none';
  600. this.setAttr('fillcolor', this.renderer.color(value, element, key, this));
  601. }
  602. },
  603. 'fill-opacitySetter': function (value, key, element) {
  604. createElement(this.renderer.prepVML(['<', key.split('-')[0], ' opacity="', value, '"/>']), null, null, element);
  605. },
  606. // Don't bother - animation is too slow and filters introduce artifacts
  607. opacitySetter: noop,
  608. rotationSetter: function (value, key, element) {
  609. var style = element.style;
  610. // style is for #1873:
  611. this[key] = style[key] = value;
  612. // Correction for the 1x1 size of the shape container. Used in gauge
  613. // needles.
  614. style.left =
  615. -Math.round(Math.sin(value * deg2rad) + 1) + 'px';
  616. style.top =
  617. Math.round(Math.cos(value * deg2rad)) + 'px';
  618. },
  619. strokeSetter: function (value, key, element) {
  620. this.setAttr('strokecolor', this.renderer.color(value, element, key, this));
  621. },
  622. 'stroke-widthSetter': function (value, key, element) {
  623. element.stroked = !!value; // VML "stroked" attribute
  624. this[key] = value; // used in getter, issue #113
  625. if (isNumber(value)) {
  626. value += 'px';
  627. }
  628. this.setAttr('strokeweight', value);
  629. },
  630. titleSetter: function (value, key) {
  631. this.setAttr(key, value);
  632. },
  633. visibilitySetter: function (value, key, element) {
  634. // Handle inherited visibility
  635. if (value === 'inherit') {
  636. value = 'visible';
  637. }
  638. // Let the shadow follow the main element
  639. if (this.shadows) {
  640. this.shadows.forEach(function (shadow) {
  641. shadow.style[key] = value;
  642. });
  643. }
  644. // Instead of toggling the visibility CSS property, move the div out
  645. // of the viewport. This works around #61 and #586
  646. if (element.nodeName === 'DIV') {
  647. value = value === 'hidden' ? '-999em' : 0;
  648. // In order to redraw, IE7 needs the div to be visible when
  649. // tucked away outside the viewport. So the visibility is
  650. // actually opposite of the expected value. This applies to the
  651. // tooltip only.
  652. if (!this.docMode8) {
  653. element.style[key] = value ? 'visible' : 'hidden';
  654. }
  655. key = 'top';
  656. }
  657. element.style[key] = value;
  658. },
  659. xSetter: function (value, key, element) {
  660. this[key] = value; // used in getter
  661. if (key === 'x') {
  662. key = 'left';
  663. }
  664. else if (key === 'y') {
  665. key = 'top';
  666. }
  667. // clipping rectangle special
  668. if (this.updateClipping) {
  669. // the key is now 'left' or 'top' for 'x' and 'y'
  670. this[key] = value;
  671. this.updateClipping();
  672. }
  673. else {
  674. // normal
  675. element.style[key] = value;
  676. }
  677. },
  678. zIndexSetter: function (value, key, element) {
  679. element.style[key] = value;
  680. },
  681. fillGetter: function () {
  682. return this.getAttr('fillcolor') || '';
  683. },
  684. strokeGetter: function () {
  685. return this.getAttr('strokecolor') || '';
  686. },
  687. // #7850
  688. classGetter: function () {
  689. return this.getAttr('className') || '';
  690. }
  691. };
  692. VMLElement['stroke-opacitySetter'] =
  693. VMLElement['fill-opacitySetter'];
  694. H.VMLElement = VMLElement = extendClass(SVGElement, VMLElement);
  695. // Some shared setters
  696. VMLElement.prototype.ySetter =
  697. VMLElement.prototype.widthSetter =
  698. VMLElement.prototype.heightSetter =
  699. VMLElement.prototype.xSetter;
  700. /**
  701. * The VML renderer
  702. *
  703. * @private
  704. * @class
  705. * @name Highcharts.VMLRenderer
  706. *
  707. * @augments Highcharts.SVGRenderer
  708. */
  709. var VMLRendererExtension = {
  710. Element: VMLElement,
  711. isIE8: win.navigator.userAgent.indexOf('MSIE 8.0') > -1,
  712. /**
  713. * Initialize the VMLRenderer.
  714. *
  715. * @function Highcharts.VMLRenderer#init
  716. * @param {Highcharts.HTMLDOMElement} container
  717. * @param {number} width
  718. * @param {number} height
  719. * @return {void}
  720. */
  721. init: function (container, width, height) {
  722. var renderer = this, boxWrapper, box, css;
  723. // Extended SVGRenderer member
  724. this.crispPolyLine = SVGRenderer.prototype.crispPolyLine;
  725. renderer.alignedObjects = [];
  726. boxWrapper = renderer.createElement('div')
  727. .css({ position: 'relative' });
  728. box = boxWrapper.element;
  729. container.appendChild(boxWrapper.element);
  730. // generate the containing box
  731. renderer.isVML = true;
  732. renderer.box = box;
  733. renderer.boxWrapper = boxWrapper;
  734. renderer.gradients = {};
  735. renderer.cache = {}; // Cache for numerical bounding boxes
  736. renderer.cacheKeys = [];
  737. renderer.imgCount = 0;
  738. renderer.setSize(width, height, false);
  739. // The only way to make IE6 and IE7 print is to use a global
  740. // namespace. However, with IE8 the only way to make the dynamic
  741. // shapes visible in screen and print mode seems to be to add the
  742. // xmlns attribute and the behaviour style inline.
  743. if (!doc.namespaces.hcv) {
  744. doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
  745. // Setup default CSS (#2153, #2368, #2384)
  746. css = 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
  747. '{ behavior:url(#default#VML); display: inline-block; } ';
  748. try {
  749. doc.createStyleSheet().cssText = css;
  750. }
  751. catch (e) {
  752. doc.styleSheets[0].cssText += css;
  753. }
  754. }
  755. },
  756. /**
  757. * Detect whether the renderer is hidden. This happens when one of the
  758. * parent elements has display: none
  759. *
  760. * @function Highcharts.VMLRenderer#isHidden
  761. */
  762. isHidden: function () {
  763. return !this.box.offsetWidth;
  764. },
  765. /**
  766. * Define a clipping rectangle. In VML it is accomplished by storing the
  767. * values for setting the CSS style to all associated members.
  768. *
  769. * @function Highcharts.VMLRenderer#clipRect
  770. * @param {number|Highcharts.SizeObject} x
  771. * @param {number} y
  772. * @param {number} width
  773. * @param {number} height
  774. * @return {Highcharts.VMLElement}
  775. */
  776. clipRect: function (x, y, width, height) {
  777. // create a dummy element
  778. var clipRect = this.createElement(), isObj = isObject(x);
  779. // mimic a rectangle with its style object for automatic updating in
  780. // attr
  781. return extend(clipRect, {
  782. members: [],
  783. count: 0,
  784. left: (isObj ? x.x : x) + 1,
  785. top: (isObj ? x.y : y) + 1,
  786. width: (isObj ? x.width : width) - 1,
  787. height: (isObj ? x.height : height) - 1,
  788. getCSS: function (wrapper) {
  789. var element = wrapper.element, nodeName = element.nodeName, isShape = nodeName === 'shape', inverted = wrapper.inverted, rect = this, top = rect.top - (isShape ? element.offsetTop : 0), left = rect.left, right = left + rect.width, bottom = top + rect.height, ret = {
  790. clip: 'rect(' +
  791. Math.round(inverted ? left : top) + 'px,' +
  792. Math.round(inverted ? bottom : right) + 'px,' +
  793. Math.round(inverted ? right : bottom) + 'px,' +
  794. Math.round(inverted ? top : left) + 'px)'
  795. };
  796. // issue 74 workaround
  797. if (!inverted && wrapper.docMode8 && nodeName === 'DIV') {
  798. extend(ret, {
  799. width: right + 'px',
  800. height: bottom + 'px'
  801. });
  802. }
  803. return ret;
  804. },
  805. // used in attr and animation to update the clipping of all
  806. // members
  807. updateClipping: function () {
  808. clipRect.members.forEach(function (member) {
  809. // Member.element is falsy on deleted series, like in
  810. // stock/members/series-remove demo. Should be removed
  811. // from members, but this will do.
  812. if (member.element) {
  813. member.css(clipRect.getCSS(member));
  814. }
  815. });
  816. }
  817. });
  818. },
  819. /**
  820. * Take a color and return it if it's a string, make it a gradient if
  821. * it's a gradient configuration object, and apply opacity.
  822. *
  823. * @function Highcharts.VMLRenderer#color<T>
  824. *
  825. * @param {T} color
  826. * The color or config object
  827. *
  828. * @return {T}
  829. */
  830. color: function (colorOption, elem, prop, wrapper) {
  831. var renderer = this, colorObject, regexRgba = /^rgba/, markup, fillType, ret = 'none';
  832. // Check for linear or radial gradient
  833. if (colorOption &&
  834. colorOption.linearGradient) {
  835. fillType = 'gradient';
  836. }
  837. else if (colorOption &&
  838. colorOption.radialGradient) {
  839. fillType = 'pattern';
  840. }
  841. if (fillType) {
  842. var stopColor, stopOpacity, gradient = (colorOption.linearGradient ||
  843. colorOption.radialGradient), x1, y1, x2, y2, opacity1, opacity2, color1, color2, fillAttr = '', stops = colorOption.stops, firstStop, lastStop, colors = [], addFillNode = function () {
  844. // Add the fill subnode. When colors attribute is used,
  845. // the meanings of opacity and o:opacity2 are reversed.
  846. markup = ['<fill colors="' + colors.join(',') +
  847. '" opacity="', opacity2, '" o:opacity2="',
  848. opacity1, '" type="', fillType, '" ', fillAttr,
  849. 'focus="100%" method="any" />'];
  850. createElement(renderer.prepVML(markup), null, null, elem);
  851. };
  852. // Extend from 0 to 1
  853. firstStop = stops[0];
  854. lastStop = stops[stops.length - 1];
  855. if (firstStop[0] > 0) {
  856. stops.unshift([
  857. 0,
  858. firstStop[1]
  859. ]);
  860. }
  861. if (lastStop[0] < 1) {
  862. stops.push([
  863. 1,
  864. lastStop[1]
  865. ]);
  866. }
  867. // Compute the stops
  868. stops.forEach(function (stop, i) {
  869. if (regexRgba.test(stop[1])) {
  870. colorObject = color(stop[1]);
  871. stopColor = colorObject.get('rgb');
  872. stopOpacity = colorObject.get('a');
  873. }
  874. else {
  875. stopColor = stop[1];
  876. stopOpacity = 1;
  877. }
  878. // Build the color attribute
  879. colors.push((stop[0] * 100) + '% ' + stopColor);
  880. // Only start and end opacities are allowed, so we use the
  881. // first and the last
  882. if (!i) {
  883. opacity1 = stopOpacity;
  884. color2 = stopColor;
  885. }
  886. else {
  887. opacity2 = stopOpacity;
  888. color1 = stopColor;
  889. }
  890. });
  891. // Apply the gradient to fills only.
  892. if (prop === 'fill') {
  893. // Handle linear gradient angle
  894. if (fillType === 'gradient') {
  895. x1 = gradient.x1 || gradient[0] || 0;
  896. y1 = gradient.y1 || gradient[1] || 0;
  897. x2 = gradient.x2 || gradient[2] || 0;
  898. y2 = gradient.y2 || gradient[3] || 0;
  899. fillAttr = 'angle="' + (90 - Math.atan((y2 - y1) / // y vector
  900. (x2 - x1) // x vector
  901. ) * 180 / Math.PI) + '"';
  902. addFillNode();
  903. // Radial (circular) gradient
  904. }
  905. else {
  906. var r = gradient.r, sizex = r * 2, sizey = r * 2, cx = gradient.cx, cy = gradient.cy, radialReference = elem.radialReference, bBox, applyRadialGradient = function () {
  907. if (radialReference) {
  908. bBox = wrapper.getBBox();
  909. cx += (radialReference[0] - bBox.x) /
  910. bBox.width - 0.5;
  911. cy += (radialReference[1] - bBox.y) /
  912. bBox.height - 0.5;
  913. sizex *= radialReference[2] / bBox.width;
  914. sizey *= radialReference[2] / bBox.height;
  915. }
  916. fillAttr =
  917. 'src="' + getOptions().global.VMLRadialGradientURL +
  918. '" ' +
  919. 'size="' + sizex + ',' + sizey + '" ' +
  920. 'origin="0.5,0.5" ' +
  921. 'position="' + cx + ',' + cy + '" ' +
  922. 'color2="' + color2 + '" ';
  923. addFillNode();
  924. };
  925. // Apply radial gradient
  926. if (wrapper.added) {
  927. applyRadialGradient();
  928. }
  929. else {
  930. // We need to know the bounding box to get the size
  931. // and position right
  932. wrapper.onAdd = applyRadialGradient;
  933. }
  934. // The fill element's color attribute is broken in IE8
  935. // standards mode, so we need to set the parent shape's
  936. // fillcolor attribute instead.
  937. ret = color1;
  938. }
  939. // Gradients are not supported for VML stroke, return the first
  940. // color. #722.
  941. }
  942. else {
  943. ret = stopColor;
  944. }
  945. // If the color is an rgba color, split it and add a fill node
  946. // to hold the opacity component
  947. }
  948. else if (regexRgba.test(colorOption) && elem.tagName !== 'IMG') {
  949. colorObject = color(colorOption);
  950. wrapper[prop + '-opacitySetter'](colorObject.get('a'), prop, elem);
  951. ret = colorObject.get('rgb');
  952. }
  953. else {
  954. // 'stroke' or 'fill' node
  955. var propNodes = elem.getElementsByTagName(prop);
  956. if (propNodes.length) {
  957. propNodes[0].opacity = 1;
  958. propNodes[0].type = 'solid';
  959. }
  960. ret = colorOption;
  961. }
  962. return ret;
  963. },
  964. /**
  965. * Take a VML string and prepare it for either IE8 or IE6/IE7.
  966. *
  967. * @function Highcharts.VMLRenderer#prepVML
  968. *
  969. * @param {Array<(number|string)>} markup
  970. * A string array of the VML markup to prepare
  971. *
  972. * @return {string}
  973. */
  974. prepVML: function (markup) {
  975. var vmlStyle = 'display:inline-block;behavior:url(#default#VML);', isIE8 = this.isIE8;
  976. markup = markup.join('');
  977. if (isIE8) { // add xmlns and style inline
  978. markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
  979. if (markup.indexOf('style="') === -1) {
  980. markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
  981. }
  982. else {
  983. markup = markup.replace('style="', 'style="' + vmlStyle);
  984. }
  985. }
  986. else { // add namespace
  987. markup = markup.replace('<', '<hcv:');
  988. }
  989. return markup;
  990. },
  991. /**
  992. * Create rotated and aligned text
  993. *
  994. * @function Highcharts.VMLRenderer#text
  995. *
  996. * @param {string} str
  997. *
  998. * @param {number} x
  999. *
  1000. * @param {number} y
  1001. */
  1002. text: SVGRenderer.prototype.html,
  1003. /**
  1004. * Create and return a path element
  1005. *
  1006. * @function Highcharts.VMLRenderer#path
  1007. *
  1008. * @param {Highcharts.VMLAttributes|Highcharts.VMLPathArray} [path]
  1009. */
  1010. path: function (path) {
  1011. var attr = {
  1012. // subpixel precision down to 0.1 (width and height = 1px)
  1013. coordsize: '10 10'
  1014. };
  1015. if (isArray(path)) {
  1016. attr.d = path;
  1017. }
  1018. else if (isObject(path)) { // attributes
  1019. extend(attr, path);
  1020. }
  1021. // create the shape
  1022. return this.createElement('shape').attr(attr);
  1023. },
  1024. /**
  1025. * Create and return a circle element. In VML circles are implemented as
  1026. * shapes, which is faster than v:oval
  1027. *
  1028. * @function Highcharts.VMLRenderer#circle
  1029. * @param {number|Highcharts.Dictionary<number>} x
  1030. * @param {number} [y]
  1031. * @param {number} [r]
  1032. * @return {Highcharts.VMLElement}
  1033. */
  1034. circle: function (x, y, r) {
  1035. var circle = this.symbol('circle');
  1036. if (isObject(x)) {
  1037. r = x.r;
  1038. y = x.y;
  1039. x = x.x;
  1040. }
  1041. circle.isCircle = true; // Causes x and y to mean center (#1682)
  1042. circle.r = r;
  1043. return circle.attr({ x: x, y: y });
  1044. },
  1045. /**
  1046. * Create a group using an outer div and an inner v:group to allow
  1047. * rotating and flipping. A simple v:group would have problems with
  1048. * positioning child HTML elements and CSS clip.
  1049. *
  1050. * @function Highcharts.VMLRenderer#g
  1051. *
  1052. * @param {string} name
  1053. * The name of the group
  1054. *
  1055. * @return {Highcharts.VMLElement}
  1056. */
  1057. g: function (name) {
  1058. var wrapper, attribs;
  1059. // set the class name
  1060. if (name) {
  1061. attribs = {
  1062. 'className': 'highcharts-' + name,
  1063. 'class': 'highcharts-' + name
  1064. };
  1065. }
  1066. // the div to hold HTML and clipping
  1067. wrapper = this.createElement('div').attr(attribs);
  1068. return wrapper;
  1069. },
  1070. /**
  1071. * VML override to create a regular HTML image.
  1072. *
  1073. * @function Highcharts.VMLRenderer#image
  1074. *
  1075. * @param {string} src
  1076. *
  1077. * @param {number} x
  1078. *
  1079. * @param {number} y
  1080. *
  1081. * @param {number} width
  1082. *
  1083. * @param {number} height
  1084. * @return {Highcharts.VMLElement}
  1085. */
  1086. image: function (src, x, y, width, height) {
  1087. var obj = this.createElement('img').attr({ src: src });
  1088. if (arguments.length > 1) {
  1089. obj.attr({
  1090. x: x,
  1091. y: y,
  1092. width: width,
  1093. height: height
  1094. });
  1095. }
  1096. return obj;
  1097. },
  1098. /**
  1099. * For rectangles, VML uses a shape for rect to overcome bugs and
  1100. * rotation problems
  1101. *
  1102. * @function Highcharts.VMLRenderer#createElement
  1103. * @param {string} nodeName
  1104. * @return {Highcharts.VMLElement}
  1105. */
  1106. createElement: function (nodeName) {
  1107. return nodeName === 'rect' ?
  1108. this.symbol(nodeName) :
  1109. SVGRenderer.prototype.createElement.call(this, nodeName);
  1110. },
  1111. /**
  1112. * In the VML renderer, each child of an inverted div (group) is
  1113. * inverted
  1114. *
  1115. * @function Highcharts.VMLRenderer#invertChild
  1116. *
  1117. * @param {Highcharts.HTMLDOMElement} element
  1118. *
  1119. * @param {Highcharts.HTMLDOMElement} parentNode
  1120. */
  1121. invertChild: function (element, parentNode) {
  1122. var ren = this, parentStyle = parentNode.style, imgStyle = element.tagName === 'IMG' && element.style; // #1111
  1123. css(element, {
  1124. flip: 'x',
  1125. left: pInt(parentStyle.width) -
  1126. (imgStyle ? pInt(imgStyle.top) : 1),
  1127. top: pInt(parentStyle.height) -
  1128. (imgStyle ? pInt(imgStyle.left) : 1),
  1129. rotation: -90
  1130. });
  1131. // Recursively invert child elements, needed for nested composite
  1132. // shapes like box plots and error bars. #1680, #1806.
  1133. [].forEach.call(element.childNodes, function (child) {
  1134. ren.invertChild(child, element);
  1135. });
  1136. },
  1137. /**
  1138. * Symbol definitions that override the parent SVG renderer's symbols
  1139. *
  1140. * @name Highcharts.VMLRenderer#symbols
  1141. * @type {Highcharts.Dictionary<Function>}
  1142. */
  1143. symbols: {
  1144. // VML specific arc function
  1145. arc: function (x, y, w, h, options) {
  1146. var start = options.start, end = options.end, radius = options.r || w || h, innerRadius = options.innerR, cosStart = Math.cos(start), sinStart = Math.sin(start), cosEnd = Math.cos(end), sinEnd = Math.sin(end), ret;
  1147. if (end - start === 0) { // no angle, don't show it.
  1148. return ['x'];
  1149. }
  1150. ret = [
  1151. 'wa',
  1152. x - radius,
  1153. y - radius,
  1154. x + radius,
  1155. y + radius,
  1156. x + radius * cosStart,
  1157. y + radius * sinStart,
  1158. x + radius * cosEnd,
  1159. y + radius * sinEnd // end y
  1160. ];
  1161. if (options.open && !innerRadius) {
  1162. ret.push('e', 'M', x, // - innerRadius,
  1163. y // - innerRadius
  1164. );
  1165. }
  1166. ret.push('at', // anti clockwise arc to
  1167. x - innerRadius, // left
  1168. y - innerRadius, // top
  1169. x + innerRadius, // right
  1170. y + innerRadius, // bottom
  1171. x + innerRadius * cosEnd, // start x
  1172. y + innerRadius * sinEnd, // start y
  1173. x + innerRadius * cosStart, // end x
  1174. y + innerRadius * sinStart, // end y
  1175. 'x', // finish path
  1176. 'e' // close
  1177. );
  1178. ret.isArc = true;
  1179. return ret;
  1180. },
  1181. // Add circle symbol path. This performs significantly faster than
  1182. // v:oval.
  1183. circle: function (x, y, w, h, wrapper) {
  1184. if (wrapper && defined(wrapper.r)) {
  1185. w = h = 2 * wrapper.r;
  1186. }
  1187. // Center correction, #1682
  1188. if (wrapper && wrapper.isCircle) {
  1189. x -= w / 2;
  1190. y -= h / 2;
  1191. }
  1192. // Return the path
  1193. return [
  1194. 'wa',
  1195. x,
  1196. y,
  1197. x + w,
  1198. y + h,
  1199. x + w,
  1200. y + h / 2,
  1201. x + w,
  1202. y + h / 2,
  1203. 'e' // close
  1204. ];
  1205. },
  1206. /**
  1207. * Add rectangle symbol path which eases rotation and omits arcsize
  1208. * problems compared to the built-in VML roundrect shape. When
  1209. * borders are not rounded, use the simpler square path, else use
  1210. * the callout path without the arrow.
  1211. */
  1212. rect: function (x, y, w, h, options) {
  1213. return SVGRenderer.prototype.symbols[!defined(options) || !options.r ? 'square' : 'callout'].call(0, x, y, w, h, options);
  1214. }
  1215. }
  1216. };
  1217. H.VMLRenderer = VMLRenderer = function () {
  1218. this.init.apply(this, arguments);
  1219. };
  1220. extend(VMLRenderer.prototype, SVGRenderer.prototype);
  1221. extend(VMLRenderer.prototype, VMLRendererExtension);
  1222. // general renderer
  1223. H.Renderer = VMLRenderer;
  1224. // 3D additions
  1225. VMLRenderer3D.compose(VMLRenderer, SVGRenderer);
  1226. }
  1227. SVGRenderer.prototype.getSpanWidth = function (wrapper, tspan) {
  1228. var renderer = this, bBox = wrapper.getBBox(true), actualWidth = bBox.width;
  1229. // Old IE cannot measure the actualWidth for SVG elements (#2314)
  1230. if (!svg && renderer.forExport) {
  1231. actualWidth = renderer.measureSpanWidth(tspan.firstChild.data, wrapper.styles);
  1232. }
  1233. return actualWidth;
  1234. };
  1235. // This method is used with exporting in old IE, when emulating SVG (see #2314)
  1236. SVGRenderer.prototype.measureSpanWidth = function (text, styles) {
  1237. var measuringSpan = doc.createElement('span'), offsetWidth, textNode = doc.createTextNode(text);
  1238. measuringSpan.appendChild(textNode);
  1239. css(measuringSpan, styles);
  1240. this.box.appendChild(measuringSpan);
  1241. offsetWidth = measuringSpan.offsetWidth;
  1242. discardElement(measuringSpan); // #2463
  1243. return offsetWidth;
  1244. };