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 | ;
|
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<')
|
137 | .replace(/>/g, '>');
|
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\(("|")(\S+)("|")\)/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(/ /g, '\u00A0') // no-break space
|
205 | .replace(/­/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]+)"/g, '$1')
|
348 | .replace(/"/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 | }));
|