UNPKG

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