d3d5eb48583be4fa83be810fe0977eda2a1de87a.svn-base 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. /**
  2. * @license Highcharts JS v4.2.5 (2016-05-06)
  3. * Exporting module
  4. *
  5. * (c) 2010-2016 Torstein Honsi
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. /* eslint indent:0 */
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. module.exports = factory;
  13. } else {
  14. factory(Highcharts);
  15. }
  16. }(function (Highcharts) {
  17. // create shortcuts
  18. var win = Highcharts.win,
  19. doc = win.document,
  20. Chart = Highcharts.Chart,
  21. addEvent = Highcharts.addEvent,
  22. removeEvent = Highcharts.removeEvent,
  23. fireEvent = Highcharts.fireEvent,
  24. createElement = Highcharts.createElement,
  25. discardElement = Highcharts.discardElement,
  26. css = Highcharts.css,
  27. merge = Highcharts.merge,
  28. each = Highcharts.each,
  29. extend = Highcharts.extend,
  30. splat = Highcharts.splat,
  31. math = Math,
  32. mathMax = math.max,
  33. isTouchDevice = Highcharts.isTouchDevice,
  34. M = 'M',
  35. L = 'L',
  36. DIV = 'div',
  37. HIDDEN = 'hidden',
  38. NONE = 'none',
  39. PREFIX = 'highcharts-',
  40. ABSOLUTE = 'absolute',
  41. PX = 'px',
  42. UNDEFINED,
  43. symbols = Highcharts.Renderer.prototype.symbols,
  44. defaultOptions = Highcharts.getOptions(),
  45. buttonOffset;
  46. // Add language
  47. extend(defaultOptions.lang, {
  48. printChart: 'Print chart',
  49. downloadPNG: 'Download PNG image',
  50. downloadJPEG: 'Download JPEG image',
  51. downloadPDF: 'Download PDF document',
  52. downloadSVG: 'Download SVG vector image',
  53. contextButtonTitle: 'Chart context menu'
  54. });
  55. // Buttons and menus are collected in a separate config option set called 'navigation'.
  56. // This can be extended later to add control buttons like zoom and pan right click menus.
  57. defaultOptions.navigation = {
  58. menuStyle: {
  59. border: '1px solid #A0A0A0',
  60. background: '#FFFFFF',
  61. padding: '5px 0'
  62. },
  63. menuItemStyle: {
  64. padding: '0 10px',
  65. background: NONE,
  66. color: '#303030',
  67. fontSize: isTouchDevice ? '14px' : '11px'
  68. },
  69. menuItemHoverStyle: {
  70. background: '#4572A5',
  71. color: '#FFFFFF'
  72. },
  73. buttonOptions: {
  74. symbolFill: '#E0E0E0',
  75. symbolSize: 14,
  76. symbolStroke: '#666',
  77. symbolStrokeWidth: 3,
  78. symbolX: 12.5,
  79. symbolY: 10.5,
  80. align: 'right',
  81. buttonSpacing: 3,
  82. height: 22,
  83. // text: null,
  84. theme: {
  85. fill: 'white', // capture hover
  86. stroke: 'none'
  87. },
  88. verticalAlign: 'top',
  89. width: 24
  90. }
  91. };
  92. // Add the export related options
  93. defaultOptions.exporting = {
  94. //enabled: true,
  95. //filename: 'chart',
  96. type: 'image/png',
  97. url: 'http://export.highcharts.com/',
  98. //width: undefined,
  99. printMaxWidth: 780,
  100. //scale: 2
  101. buttons: {
  102. contextButton: {
  103. menuClassName: PREFIX + 'contextmenu',
  104. //x: -10,
  105. symbol: 'menu',
  106. _titleKey: 'contextButtonTitle',
  107. menuItems: [{
  108. textKey: 'printChart',
  109. onclick: function () {
  110. this.print();
  111. }
  112. }, {
  113. separator: true
  114. }, {
  115. textKey: 'downloadPNG',
  116. onclick: function () {
  117. this.exportChart();
  118. }
  119. }, {
  120. textKey: 'downloadJPEG',
  121. onclick: function () {
  122. this.exportChart({
  123. type: 'image/jpeg'
  124. });
  125. }
  126. }, {
  127. textKey: 'downloadPDF',
  128. onclick: function () {
  129. this.exportChart({
  130. type: 'application/pdf'
  131. });
  132. }
  133. }, {
  134. textKey: 'downloadSVG',
  135. onclick: function () {
  136. this.exportChart({
  137. type: 'image/svg+xml'
  138. });
  139. }
  140. }
  141. // Enable this block to add "View SVG" to the dropdown menu
  142. /*
  143. ,{
  144. text: 'View SVG',
  145. onclick: function () {
  146. var svg = this.getSVG()
  147. .replace(/</g, '\n&lt;')
  148. .replace(/>/g, '&gt;');
  149. doc.body.innerHTML = '<pre>' + svg + '</pre>';
  150. }
  151. } // */
  152. ]
  153. }
  154. }
  155. };
  156. // Add the Highcharts.post utility
  157. Highcharts.post = function (url, data, formAttributes) {
  158. var name,
  159. form;
  160. // create the form
  161. form = createElement('form', merge({
  162. method: 'post',
  163. action: url,
  164. enctype: 'multipart/form-data'
  165. }, formAttributes), {
  166. display: NONE
  167. }, doc.body);
  168. // add the data
  169. for (name in data) {
  170. createElement('input', {
  171. type: HIDDEN,
  172. name: name,
  173. value: data[name]
  174. }, null, form);
  175. }
  176. // submit
  177. form.submit();
  178. // clean up
  179. discardElement(form);
  180. };
  181. extend(Chart.prototype, {
  182. /**
  183. * A collection of regex fixes on the produces SVG to account for expando properties,
  184. * browser bugs, VML problems and other. Returns a cleaned SVG.
  185. */
  186. sanitizeSVG: function (svg) {
  187. return svg
  188. .replace(/zIndex="[^"]+"/g, '')
  189. .replace(/isShadow="[^"]+"/g, '')
  190. .replace(/symbolName="[^"]+"/g, '')
  191. .replace(/jQuery[0-9]+="[^"]+"/g, '')
  192. .replace(/url\([^#]+#/g, 'url(#')
  193. .replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
  194. .replace(/ (NS[0-9]+\:)?href=/g, ' xlink:href=') // #3567
  195. .replace(/\n/, ' ')
  196. // Any HTML added to the container after the SVG (#894)
  197. .replace(/<\/svg>.*?$/, '</svg>')
  198. // Batik doesn't support rgba fills and strokes (#3095)
  199. .replace(/(fill|stroke)="rgba\(([ 0-9]+,[ 0-9]+,[ 0-9]+),([ 0-9\.]+)\)"/g, '$1="rgb($2)" $1-opacity="$3"')
  200. /* This fails in IE < 8
  201. .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
  202. return s2 +'.'+ s3[0];
  203. })*/
  204. // Replace HTML entities, issue #347
  205. .replace(/&nbsp;/g, '\u00A0') // no-break space
  206. .replace(/&shy;/g, '\u00AD') // soft hyphen
  207. // IE specific
  208. .replace(/<IMG /g, '<image ')
  209. .replace(/<(\/?)TITLE>/g, '<$1title>')
  210. .replace(/height=([^" ]+)/g, 'height="$1"')
  211. .replace(/width=([^" ]+)/g, 'width="$1"')
  212. .replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>')
  213. .replace(/ id=([^" >]+)/g, ' id="$1"') // #4003
  214. .replace(/class=([^" >]+)/g, 'class="$1"')
  215. .replace(/ transform /g, ' ')
  216. .replace(/:(path|rect)/g, '$1')
  217. .replace(/style="([^"]+)"/g, function (s) {
  218. return s.toLowerCase();
  219. });
  220. },
  221. /**
  222. * Return innerHTML of chart. Used as hook for plugins.
  223. */
  224. getChartHTML: function () {
  225. return this.container.innerHTML;
  226. },
  227. /**
  228. * Return an SVG representation of the chart
  229. *
  230. * @param additionalOptions {Object} Additional chart options for the generated SVG representation
  231. */
  232. getSVG: function (additionalOptions) {
  233. var chart = this,
  234. chartCopy,
  235. sandbox,
  236. svg,
  237. seriesOptions,
  238. sourceWidth,
  239. sourceHeight,
  240. cssWidth,
  241. cssHeight,
  242. html,
  243. options = merge(chart.options, additionalOptions), // copy the options and add extra options
  244. allowHTML = options.exporting.allowHTML;
  245. // IE compatibility hack for generating SVG content that it doesn't really understand
  246. if (!doc.createElementNS) {
  247. doc.createElementNS = function (ns, tagName) {
  248. return doc.createElement(tagName);
  249. };
  250. }
  251. // create a sandbox where a new chart will be generated
  252. sandbox = createElement(DIV, null, {
  253. position: ABSOLUTE,
  254. top: '-9999em',
  255. width: chart.chartWidth + PX,
  256. height: chart.chartHeight + PX
  257. }, doc.body);
  258. // get the source size
  259. cssWidth = chart.renderTo.style.width;
  260. cssHeight = chart.renderTo.style.height;
  261. sourceWidth = options.exporting.sourceWidth ||
  262. options.chart.width ||
  263. (/px$/.test(cssWidth) && parseInt(cssWidth, 10)) ||
  264. 600;
  265. sourceHeight = options.exporting.sourceHeight ||
  266. options.chart.height ||
  267. (/px$/.test(cssHeight) && parseInt(cssHeight, 10)) ||
  268. 400;
  269. // override some options
  270. extend(options.chart, {
  271. animation: false,
  272. renderTo: sandbox,
  273. forExport: true,
  274. renderer: 'SVGRenderer',
  275. width: sourceWidth,
  276. height: sourceHeight
  277. });
  278. options.exporting.enabled = false; // hide buttons in print
  279. delete options.data; // #3004
  280. // prepare for replicating the chart
  281. options.series = [];
  282. each(chart.series, function (serie) {
  283. seriesOptions = merge(serie.userOptions, { // #4912
  284. animation: false, // turn off animation
  285. enableMouseTracking: false,
  286. showCheckbox: false,
  287. visible: serie.visible
  288. });
  289. if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
  290. options.series.push(seriesOptions);
  291. }
  292. });
  293. // Axis options must be merged in one by one, since it may be an array or an object (#2022, #3900)
  294. if (additionalOptions) {
  295. each(['xAxis', 'yAxis'], function (axisType) {
  296. each(splat(additionalOptions[axisType]), function (axisOptions, i) {
  297. options[axisType][i] = merge(options[axisType][i], axisOptions);
  298. });
  299. });
  300. }
  301. // generate the chart copy
  302. chartCopy = new Highcharts.Chart(options, chart.callback);
  303. // reflect axis extremes in the export
  304. each(['xAxis', 'yAxis'], function (axisType) {
  305. each(chart[axisType], function (axis, i) {
  306. var axisCopy = chartCopy[axisType][i],
  307. extremes = axis.getExtremes(),
  308. userMin = extremes.userMin,
  309. userMax = extremes.userMax;
  310. if (axisCopy && (userMin !== UNDEFINED || userMax !== UNDEFINED)) {
  311. axisCopy.setExtremes(userMin, userMax, true, false);
  312. }
  313. });
  314. });
  315. // get the SVG from the container's innerHTML
  316. svg = chartCopy.getChartHTML();
  317. // free up memory
  318. options = null;
  319. chartCopy.destroy();
  320. discardElement(sandbox);
  321. // Move HTML into a foreignObject
  322. if (allowHTML) {
  323. html = svg.match(/<\/svg>(.*?$)/);
  324. if (html) {
  325. html = '<foreignObject x="0" y="0" width="200" height="200">' +
  326. '<body xmlns="http://www.w3.org/1999/xhtml">' +
  327. html[1] +
  328. '</body>' +
  329. '</foreignObject>';
  330. svg = svg.replace('</svg>', html + '</svg>');
  331. }
  332. }
  333. // sanitize
  334. svg = this.sanitizeSVG(svg);
  335. // IE9 beta bugs with innerHTML. Test again with final IE9.
  336. svg = svg.replace(/(url\(#highcharts-[0-9]+)&quot;/g, '$1')
  337. .replace(/&quot;/g, '\'');
  338. return svg;
  339. },
  340. getSVGForExport: function (options, chartOptions) {
  341. var chartExportingOptions = this.options.exporting;
  342. return this.getSVG(merge(
  343. { chart: { borderRadius: 0 } },
  344. chartExportingOptions.chartOptions,
  345. chartOptions,
  346. {
  347. exporting: {
  348. sourceWidth: (options && options.sourceWidth) || chartExportingOptions.sourceWidth,
  349. sourceHeight: (options && options.sourceHeight) || chartExportingOptions.sourceHeight
  350. }
  351. }
  352. ));
  353. },
  354. /**
  355. * Submit the SVG representation of the chart to the server
  356. * @param {Object} options Exporting options. Possible members are url, type, width and formAttributes.
  357. * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
  358. */
  359. exportChart: function (options, chartOptions) {
  360. var svg = this.getSVGForExport(options, chartOptions);
  361. // merge the options
  362. options = merge(this.options.exporting, options);
  363. // do the post
  364. Highcharts.post(options.url, {
  365. filename: options.filename || 'chart',
  366. type: options.type,
  367. width: options.width || 0, // IE8 fails to post undefined correctly, so use 0
  368. scale: options.scale || 2,
  369. svg: svg
  370. }, options.formAttributes);
  371. },
  372. /**
  373. * Print the chart
  374. */
  375. print: function () {
  376. var chart = this,
  377. container = chart.container,
  378. origDisplay = [],
  379. origParent = container.parentNode,
  380. body = doc.body,
  381. childNodes = body.childNodes,
  382. printMaxWidth = chart.options.exporting.printMaxWidth,
  383. hasUserSize,
  384. resetParams,
  385. handleMaxWidth;
  386. if (chart.isPrinting) { // block the button while in printing mode
  387. return;
  388. }
  389. chart.isPrinting = true;
  390. chart.pointer.reset(null, 0);
  391. fireEvent(chart, 'beforePrint');
  392. // Handle printMaxWidth
  393. handleMaxWidth = printMaxWidth && chart.chartWidth > printMaxWidth;
  394. if (handleMaxWidth) {
  395. hasUserSize = chart.hasUserSize;
  396. resetParams = [chart.chartWidth, chart.chartHeight, false];
  397. chart.setSize(printMaxWidth, chart.chartHeight, false);
  398. }
  399. // hide all body content
  400. each(childNodes, function (node, i) {
  401. if (node.nodeType === 1) {
  402. origDisplay[i] = node.style.display;
  403. node.style.display = NONE;
  404. }
  405. });
  406. // pull out the chart
  407. body.appendChild(container);
  408. // print
  409. win.focus(); // #1510
  410. win.print();
  411. // allow the browser to prepare before reverting
  412. setTimeout(function () {
  413. // put the chart back in
  414. origParent.appendChild(container);
  415. // restore all body content
  416. each(childNodes, function (node, i) {
  417. if (node.nodeType === 1) {
  418. node.style.display = origDisplay[i];
  419. }
  420. });
  421. chart.isPrinting = false;
  422. // Reset printMaxWidth
  423. if (handleMaxWidth) {
  424. chart.setSize.apply(chart, resetParams);
  425. chart.hasUserSize = hasUserSize;
  426. }
  427. fireEvent(chart, 'afterPrint');
  428. }, 1000);
  429. },
  430. /**
  431. * Display a popup menu for choosing the export type
  432. *
  433. * @param {String} className An identifier for the menu
  434. * @param {Array} items A collection with text and onclicks for the items
  435. * @param {Number} x The x position of the opener button
  436. * @param {Number} y The y position of the opener button
  437. * @param {Number} width The width of the opener button
  438. * @param {Number} height The height of the opener button
  439. */
  440. contextMenu: function (className, items, x, y, width, height, button) {
  441. var chart = this,
  442. navOptions = chart.options.navigation,
  443. menuItemStyle = navOptions.menuItemStyle,
  444. chartWidth = chart.chartWidth,
  445. chartHeight = chart.chartHeight,
  446. cacheName = 'cache-' + className,
  447. menu = chart[cacheName],
  448. menuPadding = mathMax(width, height), // for mouse leave detection
  449. boxShadow = '3px 3px 10px #888',
  450. innerMenu,
  451. hide,
  452. hideTimer,
  453. menuStyle,
  454. docMouseUpHandler = function (e) {
  455. if (!chart.pointer.inClass(e.target, className)) {
  456. hide();
  457. }
  458. };
  459. // create the menu only the first time
  460. if (!menu) {
  461. // create a HTML element above the SVG
  462. chart[cacheName] = menu = createElement(DIV, {
  463. className: className
  464. }, {
  465. position: ABSOLUTE,
  466. zIndex: 1000,
  467. padding: menuPadding + PX
  468. }, chart.container);
  469. innerMenu = createElement(DIV, null,
  470. extend({
  471. MozBoxShadow: boxShadow,
  472. WebkitBoxShadow: boxShadow,
  473. boxShadow: boxShadow
  474. }, navOptions.menuStyle), menu);
  475. // hide on mouse out
  476. hide = function () {
  477. css(menu, { display: NONE });
  478. if (button) {
  479. button.setState(0);
  480. }
  481. chart.openMenu = false;
  482. };
  483. // Hide the menu some time after mouse leave (#1357)
  484. addEvent(menu, 'mouseleave', function () {
  485. hideTimer = setTimeout(hide, 500);
  486. });
  487. addEvent(menu, 'mouseenter', function () {
  488. clearTimeout(hideTimer);
  489. });
  490. // Hide it on clicking or touching outside the menu (#2258, #2335, #2407)
  491. addEvent(doc, 'mouseup', docMouseUpHandler);
  492. addEvent(chart, 'destroy', function () {
  493. removeEvent(doc, 'mouseup', docMouseUpHandler);
  494. });
  495. // create the items
  496. each(items, function (item) {
  497. if (item) {
  498. var element = item.separator ?
  499. createElement('hr', null, null, innerMenu) :
  500. createElement(DIV, {
  501. onmouseover: function () {
  502. css(this, navOptions.menuItemHoverStyle);
  503. },
  504. onmouseout: function () {
  505. css(this, menuItemStyle);
  506. },
  507. onclick: function (e) {
  508. if (e) { // IE7
  509. e.stopPropagation();
  510. }
  511. hide();
  512. if (item.onclick) {
  513. item.onclick.apply(chart, arguments);
  514. }
  515. },
  516. innerHTML: item.text || chart.options.lang[item.textKey]
  517. }, extend({
  518. cursor: 'pointer'
  519. }, menuItemStyle), innerMenu);
  520. // Keep references to menu divs to be able to destroy them
  521. chart.exportDivElements.push(element);
  522. }
  523. });
  524. // Keep references to menu and innerMenu div to be able to destroy them
  525. chart.exportDivElements.push(innerMenu, menu);
  526. chart.exportMenuWidth = menu.offsetWidth;
  527. chart.exportMenuHeight = menu.offsetHeight;
  528. }
  529. menuStyle = { display: 'block' };
  530. // if outside right, right align it
  531. if (x + chart.exportMenuWidth > chartWidth) {
  532. menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
  533. } else {
  534. menuStyle.left = (x - menuPadding) + PX;
  535. }
  536. // if outside bottom, bottom align it
  537. if (y + height + chart.exportMenuHeight > chartHeight && button.alignOptions.verticalAlign !== 'top') {
  538. menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
  539. } else {
  540. menuStyle.top = (y + height - menuPadding) + PX;
  541. }
  542. css(menu, menuStyle);
  543. chart.openMenu = true;
  544. },
  545. /**
  546. * Add the export button to the chart
  547. */
  548. addButton: function (options) {
  549. var chart = this,
  550. renderer = chart.renderer,
  551. btnOptions = merge(chart.options.navigation.buttonOptions, options),
  552. onclick = btnOptions.onclick,
  553. menuItems = btnOptions.menuItems,
  554. symbol,
  555. button,
  556. symbolAttr = {
  557. stroke: btnOptions.symbolStroke,
  558. fill: btnOptions.symbolFill
  559. },
  560. symbolSize = btnOptions.symbolSize || 12;
  561. if (!chart.btnCount) {
  562. chart.btnCount = 0;
  563. }
  564. // Keeps references to the button elements
  565. if (!chart.exportDivElements) {
  566. chart.exportDivElements = [];
  567. chart.exportSVGElements = [];
  568. }
  569. if (btnOptions.enabled === false) {
  570. return;
  571. }
  572. var attr = btnOptions.theme,
  573. states = attr.states,
  574. hover = states && states.hover,
  575. select = states && states.select,
  576. callback;
  577. delete attr.states;
  578. if (onclick) {
  579. callback = function (e) {
  580. e.stopPropagation();
  581. onclick.call(chart, e);
  582. };
  583. } else if (menuItems) {
  584. callback = function () {
  585. chart.contextMenu(
  586. button.menuClassName,
  587. menuItems,
  588. button.translateX,
  589. button.translateY,
  590. button.width,
  591. button.height,
  592. button
  593. );
  594. button.setState(2);
  595. };
  596. }
  597. if (btnOptions.text && btnOptions.symbol) {
  598. attr.paddingLeft = Highcharts.pick(attr.paddingLeft, 25);
  599. } else if (!btnOptions.text) {
  600. extend(attr, {
  601. width: btnOptions.width,
  602. height: btnOptions.height,
  603. padding: 0
  604. });
  605. }
  606. button = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select)
  607. .attr({
  608. title: chart.options.lang[btnOptions._titleKey],
  609. 'stroke-linecap': 'round',
  610. zIndex: 3 // #4955
  611. });
  612. button.menuClassName = options.menuClassName || PREFIX + 'menu-' + chart.btnCount++;
  613. if (btnOptions.symbol) {
  614. symbol = renderer.symbol(
  615. btnOptions.symbol,
  616. btnOptions.symbolX - (symbolSize / 2),
  617. btnOptions.symbolY - (symbolSize / 2),
  618. symbolSize,
  619. symbolSize
  620. )
  621. .attr(extend(symbolAttr, {
  622. 'stroke-width': btnOptions.symbolStrokeWidth || 1,
  623. zIndex: 1
  624. })).add(button);
  625. }
  626. button.add()
  627. .align(extend(btnOptions, {
  628. width: button.width,
  629. x: Highcharts.pick(btnOptions.x, buttonOffset) // #1654
  630. }), true, 'spacingBox');
  631. buttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1);
  632. chart.exportSVGElements.push(button, symbol);
  633. },
  634. /**
  635. * Destroy the buttons.
  636. */
  637. destroyExport: function (e) {
  638. var chart = e.target,
  639. i,
  640. elem;
  641. // Destroy the extra buttons added
  642. for (i = 0; i < chart.exportSVGElements.length; i++) {
  643. elem = chart.exportSVGElements[i];
  644. // Destroy and null the svg/vml elements
  645. if (elem) { // #1822
  646. elem.onclick = elem.ontouchstart = null;
  647. chart.exportSVGElements[i] = elem.destroy();
  648. }
  649. }
  650. // Destroy the divs for the menu
  651. for (i = 0; i < chart.exportDivElements.length; i++) {
  652. elem = chart.exportDivElements[i];
  653. // Remove the event handler
  654. removeEvent(elem, 'mouseleave');
  655. // Remove inline events
  656. chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;
  657. // Destroy the div by moving to garbage bin
  658. discardElement(elem);
  659. }
  660. }
  661. });
  662. symbols.menu = function (x, y, width, height) {
  663. var arr = [
  664. M, x, y + 2.5,
  665. L, x + width, y + 2.5,
  666. M, x, y + height / 2 + 0.5,
  667. L, x + width, y + height / 2 + 0.5,
  668. M, x, y + height - 1.5,
  669. L, x + width, y + height - 1.5
  670. ];
  671. return arr;
  672. };
  673. // Add the buttons on chart load
  674. Chart.prototype.callbacks.push(function (chart) {
  675. var n,
  676. exportingOptions = chart.options.exporting,
  677. buttons = exportingOptions.buttons;
  678. buttonOffset = 0;
  679. if (exportingOptions.enabled !== false) {
  680. for (n in buttons) {
  681. chart.addButton(buttons[n]);
  682. }
  683. // Destroy the export elements at chart destroy
  684. addEvent(chart, 'destroy', chart.destroyExport);
  685. }
  686. });
  687. }));