exporting.src.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  1. /**
  2. * @license Highcharts JS v5.0.6 (2016-12-07)
  3. * Exporting module
  4. *
  5. * (c) 2010-2016 Torstein Honsi
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. (function(factory) {
  10. if (typeof module === 'object' && module.exports) {
  11. module.exports = factory;
  12. } else {
  13. factory(Highcharts);
  14. }
  15. }(function(Highcharts) {
  16. (function(H) {
  17. /**
  18. * Exporting module
  19. *
  20. * (c) 2010-2016 Torstein Honsi
  21. *
  22. * License: www.highcharts.com/license
  23. */
  24. /* eslint indent:0 */
  25. 'use strict';
  26. // create shortcuts
  27. var defaultOptions = H.defaultOptions,
  28. doc = H.doc,
  29. Chart = H.Chart,
  30. addEvent = H.addEvent,
  31. removeEvent = H.removeEvent,
  32. fireEvent = H.fireEvent,
  33. createElement = H.createElement,
  34. discardElement = H.discardElement,
  35. css = H.css,
  36. merge = H.merge,
  37. pick = H.pick,
  38. each = H.each,
  39. extend = H.extend,
  40. isTouchDevice = H.isTouchDevice,
  41. win = H.win,
  42. SVGRenderer = H.SVGRenderer;
  43. var symbols = H.Renderer.prototype.symbols;
  44. // Add language
  45. extend(defaultOptions.lang, {
  46. printChart: 'Print chart',
  47. downloadPNG: 'Download PNG image',
  48. downloadJPEG: 'Download JPEG image',
  49. downloadPDF: 'Download PDF document',
  50. downloadSVG: 'Download SVG vector image',
  51. contextButtonTitle: 'Chart context menu'
  52. });
  53. // Buttons and menus are collected in a separate config option set called 'navigation'.
  54. // This can be extended later to add control buttons like zoom and pan right click menus.
  55. defaultOptions.navigation = {
  56. buttonOptions: {
  57. theme: {},
  58. symbolSize: 14,
  59. symbolX: 12.5,
  60. symbolY: 10.5,
  61. align: 'right',
  62. buttonSpacing: 3,
  63. height: 22,
  64. // text: null,
  65. verticalAlign: 'top',
  66. width: 24
  67. }
  68. };
  69. // Add the export related options
  70. defaultOptions.exporting = {
  71. //enabled: true,
  72. //filename: 'chart',
  73. type: 'image/png',
  74. url: 'https://export.highcharts.com/',
  75. //width: undefined,
  76. printMaxWidth: 780,
  77. scale: 2,
  78. buttons: {
  79. contextButton: {
  80. className: 'highcharts-contextbutton',
  81. menuClassName: 'highcharts-contextmenu',
  82. //x: -10,
  83. symbol: 'menu',
  84. _titleKey: 'contextButtonTitle',
  85. menuItems: [{
  86. textKey: 'printChart',
  87. onclick: function() {
  88. this.print();
  89. }
  90. }, {
  91. separator: true
  92. }, {
  93. textKey: 'downloadPNG',
  94. onclick: function() {
  95. this.exportChart();
  96. }
  97. }, {
  98. textKey: 'downloadJPEG',
  99. onclick: function() {
  100. this.exportChart({
  101. type: 'image/jpeg'
  102. });
  103. }
  104. }, {
  105. textKey: 'downloadPDF',
  106. onclick: function() {
  107. this.exportChart({
  108. type: 'application/pdf'
  109. });
  110. }
  111. }, {
  112. textKey: 'downloadSVG',
  113. onclick: function() {
  114. this.exportChart({
  115. type: 'image/svg+xml'
  116. });
  117. }
  118. }
  119. // Enable this block to add "View SVG" to the dropdown menu
  120. /*
  121. ,{
  122. text: 'View SVG',
  123. onclick: function () {
  124. var svg = this.getSVG()
  125. .replace(/</g, '\n&lt;')
  126. .replace(/>/g, '&gt;');
  127. doc.body.innerHTML = '<pre>' + svg + '</pre>';
  128. }
  129. } // */
  130. ]
  131. }
  132. }
  133. };
  134. // Add the H.post utility
  135. H.post = function(url, data, formAttributes) {
  136. var name,
  137. form;
  138. // create the form
  139. form = createElement('form', merge({
  140. method: 'post',
  141. action: url,
  142. enctype: 'multipart/form-data'
  143. }, formAttributes), {
  144. display: 'none'
  145. }, doc.body);
  146. // add the data
  147. for (name in data) {
  148. createElement('input', {
  149. type: 'hidden',
  150. name: name,
  151. value: data[name]
  152. }, null, form);
  153. }
  154. // submit
  155. form.submit();
  156. // clean up
  157. discardElement(form);
  158. };
  159. extend(Chart.prototype, {
  160. /**
  161. * A collection of fixes on the produced SVG to account for expando properties,
  162. * browser bugs, VML problems and other. Returns a cleaned SVG.
  163. */
  164. sanitizeSVG: function(svg, options) {
  165. // Move HTML into a foreignObject
  166. if (options && options.exporting && options.exporting.allowHTML) {
  167. var html = svg.match(/<\/svg>(.*?$)/);
  168. if (html) {
  169. html = '<foreignObject x="0" y="0" ' +
  170. 'width="' + options.chart.width + '" ' +
  171. 'height="' + options.chart.height + '">' +
  172. '<body xmlns="http://www.w3.org/1999/xhtml">' +
  173. html[1] +
  174. '</body>' +
  175. '</foreignObject>';
  176. svg = svg.replace('</svg>', html + '</svg>');
  177. }
  178. }
  179. svg = svg
  180. .replace(/zIndex="[^"]+"/g, '')
  181. .replace(/isShadow="[^"]+"/g, '')
  182. .replace(/symbolName="[^"]+"/g, '')
  183. .replace(/jQuery[0-9]+="[^"]+"/g, '')
  184. .replace(/url\(("|&quot;)(\S+)("|&quot;)\)/g, 'url($2)')
  185. .replace(/url\([^#]+#/g, 'url(#')
  186. .replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
  187. .replace(/ (NS[0-9]+\:)?href=/g, ' xlink:href=') // #3567
  188. .replace(/\n/, ' ')
  189. // Any HTML added to the container after the SVG (#894)
  190. .replace(/<\/svg>.*?$/, '</svg>')
  191. // Batik doesn't support rgba fills and strokes (#3095)
  192. .replace(/(fill|stroke)="rgba\(([ 0-9]+,[ 0-9]+,[ 0-9]+),([ 0-9\.]+)\)"/g, '$1="rgb($2)" $1-opacity="$3"')
  193. /* This fails in IE < 8
  194. .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
  195. return s2 +'.'+ s3[0];
  196. })*/
  197. // Replace HTML entities, issue #347
  198. .replace(/&nbsp;/g, '\u00A0') // no-break space
  199. .replace(/&shy;/g, '\u00AD'); // soft hyphen
  200. return svg;
  201. },
  202. /**
  203. * Return innerHTML of chart. Used as hook for plugins.
  204. */
  205. getChartHTML: function() {
  206. this.inlineStyles();
  207. return this.container.innerHTML;
  208. },
  209. /**
  210. * Return an SVG representation of the chart.
  211. *
  212. * @param additionalOptions {Object} Additional chart options for the
  213. * generated SVG representation. For collections like `xAxis`, `yAxis` or
  214. * `series`, the additional options is either merged in to the orininal
  215. * item of the same `id`, or to the first item if a commin id is not
  216. * found.
  217. */
  218. getSVG: function(additionalOptions) {
  219. var chart = this,
  220. chartCopy,
  221. sandbox,
  222. svg,
  223. seriesOptions,
  224. sourceWidth,
  225. sourceHeight,
  226. cssWidth,
  227. cssHeight,
  228. options = merge(chart.options, additionalOptions); // copy the options and add extra options
  229. // IE compatibility hack for generating SVG content that it doesn't really understand
  230. if (!doc.createElementNS) {
  231. doc.createElementNS = function(ns, tagName) {
  232. return doc.createElement(tagName);
  233. };
  234. }
  235. // create a sandbox where a new chart will be generated
  236. sandbox = createElement('div', null, {
  237. position: 'absolute',
  238. top: '-9999em',
  239. width: chart.chartWidth + 'px',
  240. height: chart.chartHeight + 'px'
  241. }, doc.body);
  242. // get the source size
  243. cssWidth = chart.renderTo.style.width;
  244. cssHeight = chart.renderTo.style.height;
  245. sourceWidth = options.exporting.sourceWidth ||
  246. options.chart.width ||
  247. (/px$/.test(cssWidth) && parseInt(cssWidth, 10)) ||
  248. 600;
  249. sourceHeight = options.exporting.sourceHeight ||
  250. options.chart.height ||
  251. (/px$/.test(cssHeight) && parseInt(cssHeight, 10)) ||
  252. 400;
  253. // override some options
  254. extend(options.chart, {
  255. animation: false,
  256. renderTo: sandbox,
  257. forExport: true,
  258. renderer: 'SVGRenderer',
  259. width: sourceWidth,
  260. height: sourceHeight
  261. });
  262. options.exporting.enabled = false; // hide buttons in print
  263. delete options.data; // #3004
  264. // prepare for replicating the chart
  265. options.series = [];
  266. each(chart.series, function(serie) {
  267. seriesOptions = merge(serie.userOptions, { // #4912
  268. animation: false, // turn off animation
  269. enableMouseTracking: false,
  270. showCheckbox: false,
  271. visible: serie.visible
  272. });
  273. if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
  274. options.series.push(seriesOptions);
  275. }
  276. });
  277. // Assign an internal key to ensure a one-to-one mapping (#5924)
  278. each(chart.axes, function(axis) {
  279. axis.userOptions.internalKey = H.uniqueKey();
  280. });
  281. // generate the chart copy
  282. chartCopy = new H.Chart(options, chart.callback);
  283. // Axis options and series options (#2022, #3900, #5982)
  284. if (additionalOptions) {
  285. each(['xAxis', 'yAxis', 'series'], function(coll) {
  286. var collOptions = {};
  287. if (additionalOptions[coll]) {
  288. collOptions[coll] = additionalOptions[coll];
  289. chartCopy.update(collOptions);
  290. }
  291. });
  292. }
  293. // Reflect axis extremes in the export (#5924)
  294. each(chart.axes, function(axis) {
  295. var axisCopy = H.find(chartCopy.axes, function(copy) {
  296. return copy.options.internalKey ===
  297. axis.userOptions.internalKey;
  298. }),
  299. extremes = axis.getExtremes(),
  300. userMin = extremes.userMin,
  301. userMax = extremes.userMax;
  302. if (axisCopy && (userMin !== undefined || userMax !== undefined)) {
  303. axisCopy.setExtremes(userMin, userMax, true, false);
  304. }
  305. });
  306. // Get the SVG from the container's innerHTML
  307. svg = chartCopy.getChartHTML();
  308. svg = chart.sanitizeSVG(svg, options);
  309. // free up memory
  310. options = null;
  311. chartCopy.destroy();
  312. discardElement(sandbox);
  313. return svg;
  314. },
  315. getSVGForExport: function(options, chartOptions) {
  316. var chartExportingOptions = this.options.exporting;
  317. return this.getSVG(merge({
  318. chart: {
  319. borderRadius: 0
  320. }
  321. },
  322. chartExportingOptions.chartOptions,
  323. chartOptions, {
  324. exporting: {
  325. sourceWidth: (options && options.sourceWidth) || chartExportingOptions.sourceWidth,
  326. sourceHeight: (options && options.sourceHeight) || chartExportingOptions.sourceHeight
  327. }
  328. }
  329. ));
  330. },
  331. /**
  332. * Submit the SVG representation of the chart to the server
  333. * @param {Object} options Exporting options. Possible members are url, type, width and formAttributes.
  334. * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
  335. */
  336. exportChart: function(options, chartOptions) {
  337. var svg = this.getSVGForExport(options, chartOptions);
  338. // merge the options
  339. options = merge(this.options.exporting, options);
  340. // do the post
  341. H.post(options.url, {
  342. filename: options.filename || 'chart',
  343. type: options.type,
  344. width: options.width || 0, // IE8 fails to post undefined correctly, so use 0
  345. scale: options.scale,
  346. svg: svg
  347. }, options.formAttributes);
  348. },
  349. /**
  350. * Print the chart
  351. */
  352. print: function() {
  353. var chart = this,
  354. container = chart.container,
  355. origDisplay = [],
  356. origParent = container.parentNode,
  357. body = doc.body,
  358. childNodes = body.childNodes,
  359. printMaxWidth = chart.options.exporting.printMaxWidth,
  360. resetParams,
  361. handleMaxWidth;
  362. if (chart.isPrinting) { // block the button while in printing mode
  363. return;
  364. }
  365. chart.isPrinting = true;
  366. chart.pointer.reset(null, 0);
  367. fireEvent(chart, 'beforePrint');
  368. // Handle printMaxWidth
  369. handleMaxWidth = printMaxWidth && chart.chartWidth > printMaxWidth;
  370. if (handleMaxWidth) {
  371. resetParams = [chart.options.chart.width, undefined, false];
  372. chart.setSize(printMaxWidth, undefined, false);
  373. }
  374. // hide all body content
  375. each(childNodes, function(node, i) {
  376. if (node.nodeType === 1) {
  377. origDisplay[i] = node.style.display;
  378. node.style.display = 'none';
  379. }
  380. });
  381. // pull out the chart
  382. body.appendChild(container);
  383. // print
  384. win.focus(); // #1510
  385. win.print();
  386. // allow the browser to prepare before reverting
  387. setTimeout(function() {
  388. // put the chart back in
  389. origParent.appendChild(container);
  390. // restore all body content
  391. each(childNodes, function(node, i) {
  392. if (node.nodeType === 1) {
  393. node.style.display = origDisplay[i];
  394. }
  395. });
  396. chart.isPrinting = false;
  397. // Reset printMaxWidth
  398. if (handleMaxWidth) {
  399. chart.setSize.apply(chart, resetParams);
  400. }
  401. fireEvent(chart, 'afterPrint');
  402. }, 1000);
  403. },
  404. /**
  405. * Display a popup menu for choosing the export type
  406. *
  407. * @param {String} className An identifier for the menu
  408. * @param {Array} items A collection with text and onclicks for the items
  409. * @param {Number} x The x position of the opener button
  410. * @param {Number} y The y position of the opener button
  411. * @param {Number} width The width of the opener button
  412. * @param {Number} height The height of the opener button
  413. */
  414. contextMenu: function(className, items, x, y, width, height, button) {
  415. var chart = this,
  416. navOptions = chart.options.navigation,
  417. chartWidth = chart.chartWidth,
  418. chartHeight = chart.chartHeight,
  419. cacheName = 'cache-' + className,
  420. menu = chart[cacheName],
  421. menuPadding = Math.max(width, height), // for mouse leave detection
  422. innerMenu,
  423. hide,
  424. menuStyle,
  425. removeMouseUp;
  426. // create the menu only the first time
  427. if (!menu) {
  428. // create a HTML element above the SVG
  429. chart[cacheName] = menu = createElement('div', {
  430. className: className
  431. }, {
  432. position: 'absolute',
  433. zIndex: 1000,
  434. padding: menuPadding + 'px'
  435. }, chart.container);
  436. innerMenu = createElement('div', {
  437. className: 'highcharts-menu'
  438. }, null, menu);
  439. // hide on mouse out
  440. hide = function() {
  441. css(menu, {
  442. display: 'none'
  443. });
  444. if (button) {
  445. button.setState(0);
  446. }
  447. chart.openMenu = false;
  448. };
  449. // Hide the menu some time after mouse leave (#1357)
  450. addEvent(menu, 'mouseleave', function() {
  451. menu.hideTimer = setTimeout(hide, 500);
  452. });
  453. addEvent(menu, 'mouseenter', function() {
  454. clearTimeout(menu.hideTimer);
  455. });
  456. // Hide it on clicking or touching outside the menu (#2258, #2335,
  457. // #2407)
  458. removeMouseUp = addEvent(doc, 'mouseup', function(e) {
  459. if (!chart.pointer.inClass(e.target, className)) {
  460. hide();
  461. }
  462. });
  463. addEvent(chart, 'destroy', removeMouseUp);
  464. // create the items
  465. each(items, function(item) {
  466. if (item) {
  467. var element;
  468. if (item.separator) {
  469. element = createElement('hr', null, null, innerMenu);
  470. } else {
  471. element = createElement('div', {
  472. className: 'highcharts-menu-item',
  473. onclick: function(e) {
  474. if (e) { // IE7
  475. e.stopPropagation();
  476. }
  477. hide();
  478. if (item.onclick) {
  479. item.onclick.apply(chart, arguments);
  480. }
  481. },
  482. innerHTML: item.text || chart.options.lang[item.textKey]
  483. }, null, innerMenu);
  484. }
  485. // Keep references to menu divs to be able to destroy them
  486. chart.exportDivElements.push(element);
  487. }
  488. });
  489. // Keep references to menu and innerMenu div to be able to destroy them
  490. chart.exportDivElements.push(innerMenu, menu);
  491. chart.exportMenuWidth = menu.offsetWidth;
  492. chart.exportMenuHeight = menu.offsetHeight;
  493. }
  494. menuStyle = {
  495. display: 'block'
  496. };
  497. // if outside right, right align it
  498. if (x + chart.exportMenuWidth > chartWidth) {
  499. menuStyle.right = (chartWidth - x - width - menuPadding) + 'px';
  500. } else {
  501. menuStyle.left = (x - menuPadding) + 'px';
  502. }
  503. // if outside bottom, bottom align it
  504. if (y + height + chart.exportMenuHeight > chartHeight && button.alignOptions.verticalAlign !== 'top') {
  505. menuStyle.bottom = (chartHeight - y - menuPadding) + 'px';
  506. } else {
  507. menuStyle.top = (y + height - menuPadding) + 'px';
  508. }
  509. css(menu, menuStyle);
  510. chart.openMenu = true;
  511. },
  512. /**
  513. * Add the export button to the chart
  514. */
  515. addButton: function(options) {
  516. var chart = this,
  517. renderer = chart.renderer,
  518. btnOptions = merge(chart.options.navigation.buttonOptions, options),
  519. onclick = btnOptions.onclick,
  520. menuItems = btnOptions.menuItems,
  521. symbol,
  522. button,
  523. symbolSize = btnOptions.symbolSize || 12;
  524. if (!chart.btnCount) {
  525. chart.btnCount = 0;
  526. }
  527. // Keeps references to the button elements
  528. if (!chart.exportDivElements) {
  529. chart.exportDivElements = [];
  530. chart.exportSVGElements = [];
  531. }
  532. if (btnOptions.enabled === false) {
  533. return;
  534. }
  535. var attr = btnOptions.theme,
  536. states = attr.states,
  537. hover = states && states.hover,
  538. select = states && states.select,
  539. callback;
  540. delete attr.states;
  541. if (onclick) {
  542. callback = function(e) {
  543. e.stopPropagation();
  544. onclick.call(chart, e);
  545. };
  546. } else if (menuItems) {
  547. callback = function() {
  548. chart.contextMenu(
  549. button.menuClassName,
  550. menuItems,
  551. button.translateX,
  552. button.translateY,
  553. button.width,
  554. button.height,
  555. button
  556. );
  557. button.setState(2);
  558. };
  559. }
  560. if (btnOptions.text && btnOptions.symbol) {
  561. attr.paddingLeft = pick(attr.paddingLeft, 25);
  562. } else if (!btnOptions.text) {
  563. extend(attr, {
  564. width: btnOptions.width,
  565. height: btnOptions.height,
  566. padding: 0
  567. });
  568. }
  569. button = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select)
  570. .addClass(options.className)
  571. .attr({
  572. title: chart.options.lang[btnOptions._titleKey],
  573. zIndex: 3 // #4955
  574. });
  575. button.menuClassName = options.menuClassName || 'highcharts-menu-' + chart.btnCount++;
  576. if (btnOptions.symbol) {
  577. symbol = renderer.symbol(
  578. btnOptions.symbol,
  579. btnOptions.symbolX - (symbolSize / 2),
  580. btnOptions.symbolY - (symbolSize / 2),
  581. symbolSize,
  582. symbolSize
  583. )
  584. .addClass('highcharts-button-symbol')
  585. .attr({
  586. zIndex: 1
  587. }).add(button);
  588. }
  589. button.add()
  590. .align(extend(btnOptions, {
  591. width: button.width,
  592. x: pick(btnOptions.x, chart.buttonOffset) // #1654
  593. }), true, 'spacingBox');
  594. chart.buttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1);
  595. chart.exportSVGElements.push(button, symbol);
  596. },
  597. /**
  598. * Destroy the buttons.
  599. */
  600. destroyExport: function(e) {
  601. var chart = e ? e.target : this,
  602. exportSVGElements = chart.exportSVGElements,
  603. exportDivElements = chart.exportDivElements;
  604. // Destroy the extra buttons added
  605. if (exportSVGElements) {
  606. each(exportSVGElements, function(elem, i) {
  607. // Destroy and null the svg/vml elements
  608. if (elem) { // #1822
  609. elem.onclick = elem.ontouchstart = null;
  610. chart.exportSVGElements[i] = elem.destroy();
  611. }
  612. });
  613. exportSVGElements.length = 0;
  614. }
  615. // Destroy the divs for the menu
  616. if (exportDivElements) {
  617. each(exportDivElements, function(elem, i) {
  618. // Remove the event handler
  619. clearTimeout(elem.hideTimer); // #5427
  620. removeEvent(elem, 'mouseleave');
  621. // Remove inline events
  622. chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;
  623. // Destroy the div by moving to garbage bin
  624. discardElement(elem);
  625. });
  626. exportDivElements.length = 0;
  627. }
  628. }
  629. });
  630. // These ones are translated to attributes rather than styles
  631. SVGRenderer.prototype.inlineToAttributes = [
  632. 'fill',
  633. 'stroke',
  634. 'strokeLinecap',
  635. 'strokeLinejoin',
  636. 'strokeWidth',
  637. 'textAnchor',
  638. 'x',
  639. 'y'
  640. ];
  641. // These CSS properties are not inlined. Remember camelCase.
  642. SVGRenderer.prototype.inlineBlacklist = [
  643. /-/, // In Firefox, both hyphened and camelCased names are listed
  644. /^(clipPath|cssText|d|height|width)$/, // Full words
  645. /^font$/, // more specific props are set
  646. /[lL]ogical(Width|Height)$/,
  647. /perspective/,
  648. /TapHighlightColor/,
  649. /^transition/
  650. // /^text (border|color|cursor|height|webkitBorder)/
  651. ];
  652. SVGRenderer.prototype.unstyledElements = [
  653. 'clipPath',
  654. 'defs',
  655. 'desc'
  656. ];
  657. /**
  658. * Analyze inherited styles from stylesheets and add them inline
  659. *
  660. * @todo: What are the border styles for text about? In general, text has a lot of properties.
  661. * @todo: Make it work with IE9 and IE10.
  662. */
  663. Chart.prototype.inlineStyles = function() {
  664. var renderer = this.renderer,
  665. inlineToAttributes = renderer.inlineToAttributes,
  666. blacklist = renderer.inlineBlacklist,
  667. unstyledElements = renderer.unstyledElements,
  668. defaultStyles = {},
  669. dummySVG;
  670. /**
  671. * Make hyphenated property names out of camelCase
  672. */
  673. function hyphenate(prop) {
  674. return prop.replace(
  675. /([A-Z])/g,
  676. function(a, b) {
  677. return '-' + b.toLowerCase();
  678. }
  679. );
  680. }
  681. /**
  682. * Call this on all elements and recurse to children
  683. */
  684. function recurse(node) {
  685. var prop,
  686. styles,
  687. parentStyles,
  688. cssText = '',
  689. dummy,
  690. styleAttr,
  691. blacklisted,
  692. i;
  693. if (node.nodeType === 1 && unstyledElements.indexOf(node.nodeName) === -1) {
  694. styles = win.getComputedStyle(node, null);
  695. parentStyles = node.nodeName === 'svg' ? {} : win.getComputedStyle(node.parentNode, null);
  696. // Get default styles from the browser so that we don't have to add these
  697. if (!defaultStyles[node.nodeName]) {
  698. if (!dummySVG) {
  699. dummySVG = doc.createElementNS(H.SVG_NS, 'svg');
  700. dummySVG.setAttribute('version', '1.1');
  701. doc.body.appendChild(dummySVG);
  702. }
  703. dummy = doc.createElementNS(node.namespaceURI, node.nodeName);
  704. dummySVG.appendChild(dummy);
  705. defaultStyles[node.nodeName] = merge(win.getComputedStyle(dummy, null)); // Copy, so we can remove the node
  706. dummySVG.removeChild(dummy);
  707. }
  708. // Loop over all the computed styles and check whether they are in the
  709. // white list for styles or atttributes.
  710. for (prop in styles) {
  711. // Check against blacklist
  712. blacklisted = false;
  713. i = blacklist.length;
  714. while (i-- && !blacklisted) {
  715. blacklisted = blacklist[i].test(prop) || typeof styles[prop] === 'function';
  716. }
  717. if (!blacklisted) {
  718. // If parent node has the same style, it gets inherited, no need to inline it
  719. if (parentStyles[prop] !== styles[prop] && defaultStyles[node.nodeName][prop] !== styles[prop]) {
  720. // Attributes
  721. if (inlineToAttributes.indexOf(prop) !== -1) {
  722. node.setAttribute(hyphenate(prop), styles[prop]);
  723. // Styles
  724. } else {
  725. cssText += hyphenate(prop) + ':' + styles[prop] + ';';
  726. }
  727. }
  728. }
  729. }
  730. // Apply styles
  731. if (cssText) {
  732. styleAttr = node.getAttribute('style');
  733. node.setAttribute('style', (styleAttr ? styleAttr + ';' : '') + cssText);
  734. }
  735. if (node.nodeName === 'text') {
  736. return;
  737. }
  738. // Recurse
  739. each(node.children || node.childNodes, recurse);
  740. }
  741. }
  742. /**
  743. * Remove the dummy objects used to get defaults
  744. */
  745. function tearDown() {
  746. dummySVG.parentNode.removeChild(dummySVG);
  747. }
  748. recurse(this.container.querySelector('svg'));
  749. tearDown();
  750. };
  751. symbols.menu = function(x, y, width, height) {
  752. var arr = [
  753. 'M', x, y + 2.5,
  754. 'L', x + width, y + 2.5,
  755. 'M', x, y + height / 2 + 0.5,
  756. 'L', x + width, y + height / 2 + 0.5,
  757. 'M', x, y + height - 1.5,
  758. 'L', x + width, y + height - 1.5
  759. ];
  760. return arr;
  761. };
  762. // Add the buttons on chart load
  763. Chart.prototype.renderExporting = function() {
  764. var n,
  765. exportingOptions = this.options.exporting,
  766. buttons = exportingOptions.buttons,
  767. isDirty = this.isDirtyExporting || !this.exportSVGElements;
  768. this.buttonOffset = 0;
  769. if (this.isDirtyExporting) {
  770. this.destroyExport();
  771. }
  772. if (isDirty && exportingOptions.enabled !== false) {
  773. for (n in buttons) {
  774. this.addButton(buttons[n]);
  775. }
  776. this.isDirtyExporting = false;
  777. }
  778. // Destroy the export elements at chart destroy
  779. addEvent(this, 'destroy', this.destroyExport);
  780. };
  781. Chart.prototype.callbacks.push(function(chart) {
  782. function update(prop, options, redraw) {
  783. chart.isDirtyExporting = true;
  784. merge(true, chart.options[prop], options);
  785. if (pick(redraw, true)) {
  786. chart.redraw();
  787. }
  788. }
  789. chart.renderExporting();
  790. addEvent(chart, 'redraw', chart.renderExporting);
  791. // Add update methods to handle chart.update and chart.exporting.update
  792. // and chart.navigation.update.
  793. each(['exporting', 'navigation'], function(prop) {
  794. chart[prop] = {
  795. update: function(options, redraw) {
  796. update(prop, options, redraw);
  797. }
  798. };
  799. });
  800. });
  801. }(Highcharts));
  802. }));