oldie.src.js 52 KB

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