app/assets/javascripts/highcharts/modules/exporting.js in highcharts-rails-4.2.7 vs app/assets/javascripts/highcharts/modules/exporting.js in highcharts-rails-5.0.0

- old
+ new

@@ -1,798 +1,877 @@ /** - * @license Highcharts JS v4.2.7 (2016-09-21) + * @license Highcharts JS v5.0.0 (2016-09-29) * Exporting module * * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - -/* eslint indent:0 */ -(function (factory) { +(function(factory) { if (typeof module === 'object' && module.exports) { module.exports = factory; } else { factory(Highcharts); } -}(function (Highcharts) { +}(function(Highcharts) { + (function(H) { + /** + * Exporting module + * + * (c) 2010-2016 Torstein Honsi + * + * License: www.highcharts.com/license + */ -// create shortcuts -var win = Highcharts.win, - doc = win.document, - Chart = Highcharts.Chart, - addEvent = Highcharts.addEvent, - removeEvent = Highcharts.removeEvent, - fireEvent = Highcharts.fireEvent, - createElement = Highcharts.createElement, - discardElement = Highcharts.discardElement, - css = Highcharts.css, - merge = Highcharts.merge, - each = Highcharts.each, - extend = Highcharts.extend, - splat = Highcharts.splat, - math = Math, - mathMax = math.max, - isTouchDevice = Highcharts.isTouchDevice, - M = 'M', - L = 'L', - DIV = 'div', - HIDDEN = 'hidden', - NONE = 'none', - PREFIX = 'highcharts-', - ABSOLUTE = 'absolute', - PX = 'px', - UNDEFINED, - symbols = Highcharts.Renderer.prototype.symbols, - defaultOptions = Highcharts.getOptions(), - buttonOffset; + /* eslint indent:0 */ + 'use strict'; - // Add language - extend(defaultOptions.lang, { - printChart: 'Print chart', - downloadPNG: 'Download PNG image', - downloadJPEG: 'Download JPEG image', - downloadPDF: 'Download PDF document', - downloadSVG: 'Download SVG vector image', - contextButtonTitle: 'Chart context menu' - }); + // create shortcuts + var defaultOptions = H.defaultOptions, + doc = H.doc, + Chart = H.Chart, + addEvent = H.addEvent, + removeEvent = H.removeEvent, + fireEvent = H.fireEvent, + createElement = H.createElement, + discardElement = H.discardElement, + css = H.css, + merge = H.merge, + pick = H.pick, + each = H.each, + extend = H.extend, + splat = H.splat, + isTouchDevice = H.isTouchDevice, + win = H.win, + SVGRenderer = H.SVGRenderer; -// Buttons and menus are collected in a separate config option set called 'navigation'. -// This can be extended later to add control buttons like zoom and pan right click menus. -defaultOptions.navigation = { - menuStyle: { - border: '1px solid #A0A0A0', - background: '#FFFFFF', - padding: '5px 0' - }, - menuItemStyle: { - padding: '0 10px', - background: NONE, - color: '#303030', - fontSize: isTouchDevice ? '14px' : '11px' - }, - menuItemHoverStyle: { - background: '#4572A5', - color: '#FFFFFF' - }, + var symbols = H.Renderer.prototype.symbols; - buttonOptions: { - symbolFill: '#E0E0E0', - symbolSize: 14, - symbolStroke: '#666', - symbolStrokeWidth: 3, - symbolX: 12.5, - symbolY: 10.5, - align: 'right', - buttonSpacing: 3, - height: 22, - // text: null, - theme: { - fill: 'white', // capture hover - stroke: 'none' - }, - verticalAlign: 'top', - width: 24 - } -}; + // Add language + extend(defaultOptions.lang, { + printChart: 'Print chart', + downloadPNG: 'Download PNG image', + downloadJPEG: 'Download JPEG image', + downloadPDF: 'Download PDF document', + downloadSVG: 'Download SVG vector image', + contextButtonTitle: 'Chart context menu' + }); + // Buttons and menus are collected in a separate config option set called 'navigation'. + // This can be extended later to add control buttons like zoom and pan right click menus. + defaultOptions.navigation = { + buttonOptions: { + theme: {}, + symbolSize: 14, + symbolX: 12.5, + symbolY: 10.5, + align: 'right', + buttonSpacing: 3, + height: 22, + // text: null, + verticalAlign: 'top', + width: 24 + } + }; -// Add the export related options -defaultOptions.exporting = { - //enabled: true, - //filename: 'chart', - type: 'image/png', - url: 'https://export.highcharts.com/', - //width: undefined, - printMaxWidth: 780, - scale: 2, - buttons: { - contextButton: { - menuClassName: PREFIX + 'contextmenu', - //x: -10, - symbol: 'menu', - _titleKey: 'contextButtonTitle', - menuItems: [{ - textKey: 'printChart', - onclick: function () { - this.print(); - } - }, { - separator: true - }, { - textKey: 'downloadPNG', - onclick: function () { - this.exportChart(); - } - }, { - textKey: 'downloadJPEG', - onclick: function () { - this.exportChart({ - type: 'image/jpeg' - }); - } - }, { - textKey: 'downloadPDF', - onclick: function () { - this.exportChart({ - type: 'application/pdf' - }); - } - }, { - textKey: 'downloadSVG', - onclick: function () { - this.exportChart({ - type: 'image/svg+xml' - }); - } - } - // Enable this block to add "View SVG" to the dropdown menu - /* - ,{ + // Presentational attributes + merge(true, defaultOptions.navigation, { + menuStyle: { + border: '1px solid #999999', + background: '#ffffff', + padding: '5px 0' + }, + menuItemStyle: { + padding: '0.5em 1em', + background: 'none', + color: '#333333', + fontSize: isTouchDevice ? '14px' : '11px', + transition: 'background 250ms, color 250ms' + }, + menuItemHoverStyle: { + background: '#335cad', + color: '#ffffff' + }, + buttonOptions: { + symbolFill: '#666666', + symbolStroke: '#666666', + symbolStrokeWidth: 3, + theme: { + fill: '#ffffff', // capture hover + stroke: 'none', + padding: 5 + } + } + }); - text: 'View SVG', - onclick: function () { - var svg = this.getSVG() - .replace(/</g, '\n&lt;') - .replace(/>/g, '&gt;'); - doc.body.innerHTML = '<pre>' + svg + '</pre>'; - } - } // */ - ] - } - } -}; -// Add the Highcharts.post utility -Highcharts.post = function (url, data, formAttributes) { - var name, - form; + // Add the export related options + defaultOptions.exporting = { + //enabled: true, + //filename: 'chart', + type: 'image/png', + url: 'https://export.highcharts.com/', + //width: undefined, + printMaxWidth: 780, + scale: 2, + buttons: { + contextButton: { + className: 'highcharts-contextbutton', + menuClassName: 'highcharts-contextmenu', + //x: -10, + symbol: 'menu', + _titleKey: 'contextButtonTitle', + menuItems: [{ + textKey: 'printChart', + onclick: function() { + this.print(); + } + }, { + separator: true + }, { + textKey: 'downloadPNG', + onclick: function() { + this.exportChart(); + } + }, { + textKey: 'downloadJPEG', + onclick: function() { + this.exportChart({ + type: 'image/jpeg' + }); + } + }, { + textKey: 'downloadPDF', + onclick: function() { + this.exportChart({ + type: 'application/pdf' + }); + } + }, { + textKey: 'downloadSVG', + onclick: function() { + this.exportChart({ + type: 'image/svg+xml' + }); + } + } + // Enable this block to add "View SVG" to the dropdown menu + /* + ,{ - // create the form - form = createElement('form', merge({ - method: 'post', - action: url, - enctype: 'multipart/form-data' - }, formAttributes), { - display: NONE - }, doc.body); + text: 'View SVG', + onclick: function () { + var svg = this.getSVG() + .replace(/</g, '\n&lt;') + .replace(/>/g, '&gt;'); - // add the data - for (name in data) { - createElement('input', { - type: HIDDEN, - name: name, - value: data[name] - }, null, form); - } + doc.body.innerHTML = '<pre>' + svg + '</pre>'; + } + } // */ + ] + } + } + }; - // submit - form.submit(); + // Add the H.post utility + H.post = function(url, data, formAttributes) { + var name, + form; - // clean up - discardElement(form); -}; + // create the form + form = createElement('form', merge({ + method: 'post', + action: url, + enctype: 'multipart/form-data' + }, formAttributes), { + display: 'none' + }, doc.body); -extend(Chart.prototype, { + // add the data + for (name in data) { + createElement('input', { + type: 'hidden', + name: name, + value: data[name] + }, null, form); + } - /** - * A collection of regex fixes on the produces SVG to account for expando properties, - * browser bugs, VML problems and other. Returns a cleaned SVG. - */ - sanitizeSVG: function (svg) { - return svg - .replace(/zIndex="[^"]+"/g, '') - .replace(/isShadow="[^"]+"/g, '') - .replace(/symbolName="[^"]+"/g, '') - .replace(/jQuery[0-9]+="[^"]+"/g, '') - .replace(/url\(("|&quot;)(\S+)("|&quot;)\)/g, 'url($2)') - .replace(/url\([^#]+#/g, 'url(#') - .replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ') - .replace(/ (NS[0-9]+\:)?href=/g, ' xlink:href=') // #3567 - .replace(/\n/, ' ') - // Any HTML added to the container after the SVG (#894) - .replace(/<\/svg>.*?$/, '</svg>') - // Batik doesn't support rgba fills and strokes (#3095) - .replace(/(fill|stroke)="rgba\(([ 0-9]+,[ 0-9]+,[ 0-9]+),([ 0-9\.]+)\)"/g, '$1="rgb($2)" $1-opacity="$3"') - /* This fails in IE < 8 - .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight - return s2 +'.'+ s3[0]; - })*/ + // submit + form.submit(); - // Replace HTML entities, issue #347 - .replace(/&nbsp;/g, '\u00A0') // no-break space - .replace(/&shy;/g, '\u00AD') // soft hyphen + // clean up + discardElement(form); + }; - // IE specific - .replace(/<IMG /g, '<image ') - .replace(/<(\/?)TITLE>/g, '<$1title>') - .replace(/height=([^" ]+)/g, 'height="$1"') - .replace(/width=([^" ]+)/g, 'width="$1"') - .replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>') - .replace(/ id=([^" >]+)/g, ' id="$1"') // #4003 - .replace(/class=([^" >]+)/g, 'class="$1"') - .replace(/ transform /g, ' ') - .replace(/:(path|rect)/g, '$1') - .replace(/style="([^"]+)"/g, function (s) { - return s.toLowerCase(); - }); - }, + extend(Chart.prototype, { - /** - * Return innerHTML of chart. Used as hook for plugins. - */ - getChartHTML: function () { - return this.container.innerHTML; - }, + /** + * A collection of regex fixes on the produces SVG to account for expando properties, + * browser bugs, VML problems and other. Returns a cleaned SVG. + */ + sanitizeSVG: function(svg) { + svg = svg + .replace(/zIndex="[^"]+"/g, '') + .replace(/isShadow="[^"]+"/g, '') + .replace(/symbolName="[^"]+"/g, '') + .replace(/jQuery[0-9]+="[^"]+"/g, '') + .replace(/url\(("|&quot;)(\S+)("|&quot;)\)/g, 'url($2)') + .replace(/url\([^#]+#/g, 'url(#') + .replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ') + .replace(/ (NS[0-9]+\:)?href=/g, ' xlink:href=') // #3567 + .replace(/\n/, ' ') + // Any HTML added to the container after the SVG (#894) + .replace(/<\/svg>.*?$/, '</svg>') + // Batik doesn't support rgba fills and strokes (#3095) + .replace(/(fill|stroke)="rgba\(([ 0-9]+,[ 0-9]+,[ 0-9]+),([ 0-9\.]+)\)"/g, '$1="rgb($2)" $1-opacity="$3"') + /* This fails in IE < 8 + .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight + return s2 +'.'+ s3[0]; + })*/ - /** - * Return an SVG representation of the chart - * - * @param additionalOptions {Object} Additional chart options for the generated SVG representation - */ - getSVG: function (additionalOptions) { - var chart = this, - chartCopy, - sandbox, - svg, - seriesOptions, - sourceWidth, - sourceHeight, - cssWidth, - cssHeight, - html, - options = merge(chart.options, additionalOptions), // copy the options and add extra options - allowHTML = options.exporting.allowHTML; + // Replace HTML entities, issue #347 + .replace(/&nbsp;/g, '\u00A0') // no-break space + .replace(/&shy;/g, '\u00AD'); // soft hyphen - // IE compatibility hack for generating SVG content that it doesn't really understand - if (!doc.createElementNS) { - doc.createElementNS = function (ns, tagName) { - return doc.createElement(tagName); - }; - } + // IE specific + svg = svg + .replace(/<IMG /g, '<image ') + .replace(/<(\/?)TITLE>/g, '<$1title>') + .replace(/height=([^" ]+)/g, 'height="$1"') + .replace(/width=([^" ]+)/g, 'width="$1"') + .replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>') + .replace(/ id=([^" >]+)/g, ' id="$1"') // #4003 + .replace(/class=([^" >]+)/g, 'class="$1"') + .replace(/ transform /g, ' ') + .replace(/:(path|rect)/g, '$1') + .replace(/style="([^"]+)"/g, function(s) { + return s.toLowerCase(); + }); - // create a sandbox where a new chart will be generated - sandbox = createElement(DIV, null, { - position: ABSOLUTE, - top: '-9999em', - width: chart.chartWidth + PX, - height: chart.chartHeight + PX - }, doc.body); - // get the source size - cssWidth = chart.renderTo.style.width; - cssHeight = chart.renderTo.style.height; - sourceWidth = options.exporting.sourceWidth || - options.chart.width || - (/px$/.test(cssWidth) && parseInt(cssWidth, 10)) || - 600; - sourceHeight = options.exporting.sourceHeight || - options.chart.height || - (/px$/.test(cssHeight) && parseInt(cssHeight, 10)) || - 400; + return svg; + }, - // override some options - extend(options.chart, { - animation: false, - renderTo: sandbox, - forExport: true, - renderer: 'SVGRenderer', - width: sourceWidth, - height: sourceHeight - }); - options.exporting.enabled = false; // hide buttons in print - delete options.data; // #3004 + /** + * Return innerHTML of chart. Used as hook for plugins. + */ + getChartHTML: function() { - // prepare for replicating the chart - options.series = []; - each(chart.series, function (serie) { - seriesOptions = merge(serie.userOptions, { // #4912 - animation: false, // turn off animation - enableMouseTracking: false, - showCheckbox: false, - visible: serie.visible - }); + return this.container.innerHTML; + }, - if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set - options.series.push(seriesOptions); - } - }); + /** + * Return an SVG representation of the chart + * + * @param additionalOptions {Object} Additional chart options for the generated SVG representation + */ + getSVG: function(additionalOptions) { + var chart = this, + chartCopy, + sandbox, + svg, + seriesOptions, + sourceWidth, + sourceHeight, + cssWidth, + cssHeight, + html, + options = merge(chart.options, additionalOptions), // copy the options and add extra options + allowHTML = options.exporting.allowHTML; - // Axis options must be merged in one by one, since it may be an array or an object (#2022, #3900) - if (additionalOptions) { - each(['xAxis', 'yAxis'], function (axisType) { - each(splat(additionalOptions[axisType]), function (axisOptions, i) { - options[axisType][i] = merge(options[axisType][i], axisOptions); - }); - }); - } - // generate the chart copy - chartCopy = new Highcharts.Chart(options, chart.callback); + // IE compatibility hack for generating SVG content that it doesn't really understand + if (!doc.createElementNS) { + doc.createElementNS = function(ns, tagName) { + return doc.createElement(tagName); + }; + } - // reflect axis extremes in the export - each(['xAxis', 'yAxis'], function (axisType) { - each(chart[axisType], function (axis, i) { - var axisCopy = chartCopy[axisType][i], - extremes = axis.getExtremes(), - userMin = extremes.userMin, - userMax = extremes.userMax; + // create a sandbox where a new chart will be generated + sandbox = createElement('div', null, { + position: 'absolute', + top: '-9999em', + width: chart.chartWidth + 'px', + height: chart.chartHeight + 'px' + }, doc.body); - if (axisCopy && (userMin !== UNDEFINED || userMax !== UNDEFINED)) { - axisCopy.setExtremes(userMin, userMax, true, false); - } - }); - }); + // get the source size + cssWidth = chart.renderTo.style.width; + cssHeight = chart.renderTo.style.height; + sourceWidth = options.exporting.sourceWidth || + options.chart.width || + (/px$/.test(cssWidth) && parseInt(cssWidth, 10)) || + 600; + sourceHeight = options.exporting.sourceHeight || + options.chart.height || + (/px$/.test(cssHeight) && parseInt(cssHeight, 10)) || + 400; - // get the SVG from the container's innerHTML - svg = chartCopy.getChartHTML(); + // override some options + extend(options.chart, { + animation: false, + renderTo: sandbox, + forExport: true, + renderer: 'SVGRenderer', + width: sourceWidth, + height: sourceHeight + }); + options.exporting.enabled = false; // hide buttons in print + delete options.data; // #3004 - // free up memory - options = null; - chartCopy.destroy(); - discardElement(sandbox); + // prepare for replicating the chart + options.series = []; + each(chart.series, function(serie) { + seriesOptions = merge(serie.userOptions, { // #4912 + animation: false, // turn off animation + enableMouseTracking: false, + showCheckbox: false, + visible: serie.visible + }); - // Move HTML into a foreignObject - if (allowHTML) { - html = svg.match(/<\/svg>(.*?$)/); - if (html) { - html = '<foreignObject x="0" y="0" width="200" height="200">' + - '<body xmlns="http://www.w3.org/1999/xhtml">' + - html[1] + - '</body>' + - '</foreignObject>'; - svg = svg.replace('</svg>', html + '</svg>'); - } - } + if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set + options.series.push(seriesOptions); + } + }); - // sanitize - svg = this.sanitizeSVG(svg); + // Axis options must be merged in one by one, since it may be an array or an object (#2022, #3900) + if (additionalOptions) { + each(['xAxis', 'yAxis'], function(axisType) { + each(splat(additionalOptions[axisType]), function(axisOptions, i) { + options[axisType][i] = merge(options[axisType][i], axisOptions); + }); + }); + } - // IE9 beta bugs with innerHTML. Test again with final IE9. - svg = svg.replace(/(url\(#highcharts-[0-9]+)&quot;/g, '$1') - .replace(/&quot;/g, '\''); + // generate the chart copy + chartCopy = new H.Chart(options, chart.callback); - return svg; - }, + // reflect axis extremes in the export + each(['xAxis', 'yAxis'], function(axisType) { + each(chart[axisType], function(axis, i) { + var axisCopy = chartCopy[axisType][i], + extremes = axis.getExtremes(), + userMin = extremes.userMin, + userMax = extremes.userMax; - getSVGForExport: function (options, chartOptions) { - var chartExportingOptions = this.options.exporting; + if (axisCopy && (userMin !== undefined || userMax !== undefined)) { + axisCopy.setExtremes(userMin, userMax, true, false); + } + }); + }); - return this.getSVG(merge( - { chart: { borderRadius: 0 } }, - chartExportingOptions.chartOptions, - chartOptions, - { - exporting: { - sourceWidth: (options && options.sourceWidth) || chartExportingOptions.sourceWidth, - sourceHeight: (options && options.sourceHeight) || chartExportingOptions.sourceHeight - } - } - )); - }, + // get the SVG from the container's innerHTML + svg = chartCopy.getChartHTML(); - /** - * Submit the SVG representation of the chart to the server - * @param {Object} options Exporting options. Possible members are url, type, width and formAttributes. - * @param {Object} chartOptions Additional chart options for the SVG representation of the chart - */ - exportChart: function (options, chartOptions) { + // free up memory + options = null; + chartCopy.destroy(); + discardElement(sandbox); - var svg = this.getSVGForExport(options, chartOptions); + // Move HTML into a foreignObject + if (allowHTML) { + html = svg.match(/<\/svg>(.*?$)/); + if (html) { + html = '<foreignObject x="0" y="0" width="200" height="200">' + + '<body xmlns="http://www.w3.org/1999/xhtml">' + + html[1] + + '</body>' + + '</foreignObject>'; + svg = svg.replace('</svg>', html + '</svg>'); + } + } - // merge the options - options = merge(this.options.exporting, options); + // sanitize + svg = this.sanitizeSVG(svg); - // do the post - Highcharts.post(options.url, { - filename: options.filename || 'chart', - type: options.type, - width: options.width || 0, // IE8 fails to post undefined correctly, so use 0 - scale: options.scale, - svg: svg - }, options.formAttributes); + // IE9 beta bugs with innerHTML. Test again with final IE9. + svg = svg.replace(/(url\(#highcharts-[0-9]+)&quot;/g, '$1') + .replace(/&quot;/g, '\''); - }, + return svg; + }, - /** - * Print the chart - */ - print: function () { + getSVGForExport: function(options, chartOptions) { + var chartExportingOptions = this.options.exporting; - var chart = this, - container = chart.container, - origDisplay = [], - origParent = container.parentNode, - body = doc.body, - childNodes = body.childNodes, - printMaxWidth = chart.options.exporting.printMaxWidth, - resetParams, - handleMaxWidth; + return this.getSVG(merge({ + chart: { + borderRadius: 0 + } + }, + chartExportingOptions.chartOptions, + chartOptions, { + exporting: { + sourceWidth: (options && options.sourceWidth) || chartExportingOptions.sourceWidth, + sourceHeight: (options && options.sourceHeight) || chartExportingOptions.sourceHeight + } + } + )); + }, - if (chart.isPrinting) { // block the button while in printing mode - return; - } + /** + * Submit the SVG representation of the chart to the server + * @param {Object} options Exporting options. Possible members are url, type, width and formAttributes. + * @param {Object} chartOptions Additional chart options for the SVG representation of the chart + */ + exportChart: function(options, chartOptions) { - chart.isPrinting = true; - chart.pointer.reset(null, 0); + var svg = this.getSVGForExport(options, chartOptions); - fireEvent(chart, 'beforePrint'); + // merge the options + options = merge(this.options.exporting, options); - // Handle printMaxWidth - handleMaxWidth = printMaxWidth && chart.chartWidth > printMaxWidth; - if (handleMaxWidth) { - resetParams = [chart.options.chart.width, undefined, false]; - chart.setSize(printMaxWidth, undefined, false); - } + // do the post + H.post(options.url, { + filename: options.filename || 'chart', + type: options.type, + width: options.width || 0, // IE8 fails to post undefined correctly, so use 0 + scale: options.scale, + svg: svg + }, options.formAttributes); - // hide all body content - each(childNodes, function (node, i) { - if (node.nodeType === 1) { - origDisplay[i] = node.style.display; - node.style.display = NONE; - } - }); + }, - // pull out the chart - body.appendChild(container); + /** + * Print the chart + */ + print: function() { - // print - win.focus(); // #1510 - win.print(); + var chart = this, + container = chart.container, + origDisplay = [], + origParent = container.parentNode, + body = doc.body, + childNodes = body.childNodes, + printMaxWidth = chart.options.exporting.printMaxWidth, + resetParams, + handleMaxWidth; - // allow the browser to prepare before reverting - setTimeout(function () { + if (chart.isPrinting) { // block the button while in printing mode + return; + } - // put the chart back in - origParent.appendChild(container); + chart.isPrinting = true; + chart.pointer.reset(null, 0); - // restore all body content - each(childNodes, function (node, i) { - if (node.nodeType === 1) { - node.style.display = origDisplay[i]; - } - }); + fireEvent(chart, 'beforePrint'); - chart.isPrinting = false; + // Handle printMaxWidth + handleMaxWidth = printMaxWidth && chart.chartWidth > printMaxWidth; + if (handleMaxWidth) { + resetParams = [chart.options.chart.width, undefined, false]; + chart.setSize(printMaxWidth, undefined, false); + } - // Reset printMaxWidth - if (handleMaxWidth) { - chart.setSize.apply(chart, resetParams); - } + // hide all body content + each(childNodes, function(node, i) { + if (node.nodeType === 1) { + origDisplay[i] = node.style.display; + node.style.display = 'none'; + } + }); - fireEvent(chart, 'afterPrint'); + // pull out the chart + body.appendChild(container); - }, 1000); + // print + win.focus(); // #1510 + win.print(); - }, + // allow the browser to prepare before reverting + setTimeout(function() { - /** - * Display a popup menu for choosing the export type - * - * @param {String} className An identifier for the menu - * @param {Array} items A collection with text and onclicks for the items - * @param {Number} x The x position of the opener button - * @param {Number} y The y position of the opener button - * @param {Number} width The width of the opener button - * @param {Number} height The height of the opener button - */ - contextMenu: function (className, items, x, y, width, height, button) { - var chart = this, - navOptions = chart.options.navigation, - menuItemStyle = navOptions.menuItemStyle, - chartWidth = chart.chartWidth, - chartHeight = chart.chartHeight, - cacheName = 'cache-' + className, - menu = chart[cacheName], - menuPadding = mathMax(width, height), // for mouse leave detection - boxShadow = '3px 3px 10px #888', - innerMenu, - hide, - hideTimer, - menuStyle, - docMouseUpHandler = function (e) { - if (!chart.pointer.inClass(e.target, className)) { - hide(); - } - }; + // put the chart back in + origParent.appendChild(container); - // create the menu only the first time - if (!menu) { + // restore all body content + each(childNodes, function(node, i) { + if (node.nodeType === 1) { + node.style.display = origDisplay[i]; + } + }); - // create a HTML element above the SVG - chart[cacheName] = menu = createElement(DIV, { - className: className - }, { - position: ABSOLUTE, - zIndex: 1000, - padding: menuPadding + PX - }, chart.container); + chart.isPrinting = false; - innerMenu = createElement(DIV, null, - extend({ - MozBoxShadow: boxShadow, - WebkitBoxShadow: boxShadow, - boxShadow: boxShadow - }, navOptions.menuStyle), menu); + // Reset printMaxWidth + if (handleMaxWidth) { + chart.setSize.apply(chart, resetParams); + } - // hide on mouse out - hide = function () { - css(menu, { display: NONE }); - if (button) { - button.setState(0); - } - chart.openMenu = false; - }; + fireEvent(chart, 'afterPrint'); - // Hide the menu some time after mouse leave (#1357) - addEvent(menu, 'mouseleave', function () { - hideTimer = setTimeout(hide, 500); - }); - addEvent(menu, 'mouseenter', function () { - clearTimeout(hideTimer); - }); + }, 1000); + }, - // Hide it on clicking or touching outside the menu (#2258, #2335, #2407) - addEvent(doc, 'mouseup', docMouseUpHandler); - addEvent(chart, 'destroy', function () { - removeEvent(doc, 'mouseup', docMouseUpHandler); - }); + /** + * Display a popup menu for choosing the export type + * + * @param {String} className An identifier for the menu + * @param {Array} items A collection with text and onclicks for the items + * @param {Number} x The x position of the opener button + * @param {Number} y The y position of the opener button + * @param {Number} width The width of the opener button + * @param {Number} height The height of the opener button + */ + contextMenu: function(className, items, x, y, width, height, button) { + var chart = this, + navOptions = chart.options.navigation, + chartWidth = chart.chartWidth, + chartHeight = chart.chartHeight, + cacheName = 'cache-' + className, + menu = chart[cacheName], + menuPadding = Math.max(width, height), // for mouse leave detection + innerMenu, + hide, + hideTimer, + menuStyle, + docMouseUpHandler = function(e) { + if (!chart.pointer.inClass(e.target, className)) { + hide(); + } + }; + // create the menu only the first time + if (!menu) { - // create the items - each(items, function (item) { - if (item) { - var element = item.separator ? - createElement('hr', null, null, innerMenu) : - createElement(DIV, { - onmouseover: function () { - css(this, navOptions.menuItemHoverStyle); - }, - onmouseout: function () { - css(this, menuItemStyle); - }, - onclick: function (e) { - if (e) { // IE7 - e.stopPropagation(); - } - hide(); - if (item.onclick) { - item.onclick.apply(chart, arguments); - } - }, - innerHTML: item.text || chart.options.lang[item.textKey] - }, extend({ - cursor: 'pointer' - }, menuItemStyle), innerMenu); + // create a HTML element above the SVG + chart[cacheName] = menu = createElement('div', { + className: className + }, { + position: 'absolute', + zIndex: 1000, + padding: menuPadding + 'px' + }, chart.container); + innerMenu = createElement('div', { + className: 'highcharts-menu' + }, null, menu); - // Keep references to menu divs to be able to destroy them - chart.exportDivElements.push(element); - } - }); - // Keep references to menu and innerMenu div to be able to destroy them - chart.exportDivElements.push(innerMenu, menu); + // Presentational CSS + css(innerMenu, extend({ + MozBoxShadow: '3px 3px 10px #888', + WebkitBoxShadow: '3px 3px 10px #888', + boxShadow: '3px 3px 10px #888' + }, navOptions.menuStyle)); - chart.exportMenuWidth = menu.offsetWidth; - chart.exportMenuHeight = menu.offsetHeight; - } - menuStyle = { display: 'block' }; + // hide on mouse out + hide = function() { + css(menu, { + display: 'none' + }); + if (button) { + button.setState(0); + } + chart.openMenu = false; + }; - // if outside right, right align it - if (x + chart.exportMenuWidth > chartWidth) { - menuStyle.right = (chartWidth - x - width - menuPadding) + PX; - } else { - menuStyle.left = (x - menuPadding) + PX; - } - // if outside bottom, bottom align it - if (y + height + chart.exportMenuHeight > chartHeight && button.alignOptions.verticalAlign !== 'top') { - menuStyle.bottom = (chartHeight - y - menuPadding) + PX; - } else { - menuStyle.top = (y + height - menuPadding) + PX; - } + // Hide the menu some time after mouse leave (#1357) + addEvent(menu, 'mouseleave', function() { + hideTimer = setTimeout(hide, 500); + }); + addEvent(menu, 'mouseenter', function() { + clearTimeout(hideTimer); + }); - css(menu, menuStyle); - chart.openMenu = true; - }, - /** - * Add the export button to the chart - */ - addButton: function (options) { - var chart = this, - renderer = chart.renderer, - btnOptions = merge(chart.options.navigation.buttonOptions, options), - onclick = btnOptions.onclick, - menuItems = btnOptions.menuItems, - symbol, - button, - symbolAttr = { - stroke: btnOptions.symbolStroke, - fill: btnOptions.symbolFill - }, - symbolSize = btnOptions.symbolSize || 12; - if (!chart.btnCount) { - chart.btnCount = 0; - } + // Hide it on clicking or touching outside the menu (#2258, #2335, #2407) + addEvent(doc, 'mouseup', docMouseUpHandler); + addEvent(chart, 'destroy', function() { + removeEvent(doc, 'mouseup', docMouseUpHandler); + }); - // Keeps references to the button elements - if (!chart.exportDivElements) { - chart.exportDivElements = []; - chart.exportSVGElements = []; - } - if (btnOptions.enabled === false) { - return; - } + // create the items + each(items, function(item) { + if (item) { + var element; + if (item.separator) { + element = createElement('hr', null, null, innerMenu); - var attr = btnOptions.theme, - states = attr.states, - hover = states && states.hover, - select = states && states.select, - callback; + } else { + element = createElement('div', { + className: 'highcharts-menu-item', + onclick: function(e) { + if (e) { // IE7 + e.stopPropagation(); + } + hide(); + if (item.onclick) { + item.onclick.apply(chart, arguments); + } + }, + innerHTML: item.text || chart.options.lang[item.textKey] + }, null, innerMenu); - delete attr.states; - if (onclick) { - callback = function (e) { - e.stopPropagation(); - onclick.call(chart, e); - }; + element.onmouseover = function() { + css(this, navOptions.menuItemHoverStyle); + }; + element.onmouseout = function() { + css(this, navOptions.menuItemStyle); + }; + css(element, extend({ + cursor: 'pointer' + }, navOptions.menuItemStyle)); - } else if (menuItems) { - callback = function () { - chart.contextMenu( - button.menuClassName, - menuItems, - button.translateX, - button.translateY, - button.width, - button.height, - button - ); - button.setState(2); - }; - } + } + // Keep references to menu divs to be able to destroy them + chart.exportDivElements.push(element); + } + }); - if (btnOptions.text && btnOptions.symbol) { - attr.paddingLeft = Highcharts.pick(attr.paddingLeft, 25); + // Keep references to menu and innerMenu div to be able to destroy them + chart.exportDivElements.push(innerMenu, menu); - } else if (!btnOptions.text) { - extend(attr, { - width: btnOptions.width, - height: btnOptions.height, - padding: 0 - }); - } + chart.exportMenuWidth = menu.offsetWidth; + chart.exportMenuHeight = menu.offsetHeight; + } - button = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select) - .attr({ - title: chart.options.lang[btnOptions._titleKey], - 'stroke-linecap': 'round', - zIndex: 3 // #4955 - }); - button.menuClassName = options.menuClassName || PREFIX + 'menu-' + chart.btnCount++; + menuStyle = { + display: 'block' + }; - if (btnOptions.symbol) { - symbol = renderer.symbol( - btnOptions.symbol, - btnOptions.symbolX - (symbolSize / 2), - btnOptions.symbolY - (symbolSize / 2), - symbolSize, - symbolSize - ) - .attr(extend(symbolAttr, { - 'stroke-width': btnOptions.symbolStrokeWidth || 1, - zIndex: 1 - })).add(button); - } + // if outside right, right align it + if (x + chart.exportMenuWidth > chartWidth) { + menuStyle.right = (chartWidth - x - width - menuPadding) + 'px'; + } else { + menuStyle.left = (x - menuPadding) + 'px'; + } + // if outside bottom, bottom align it + if (y + height + chart.exportMenuHeight > chartHeight && button.alignOptions.verticalAlign !== 'top') { + menuStyle.bottom = (chartHeight - y - menuPadding) + 'px'; + } else { + menuStyle.top = (y + height - menuPadding) + 'px'; + } - button.add() - .align(extend(btnOptions, { - width: button.width, - x: Highcharts.pick(btnOptions.x, buttonOffset) // #1654 - }), true, 'spacingBox'); + css(menu, menuStyle); + chart.openMenu = true; + }, - buttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1); + /** + * Add the export button to the chart + */ + addButton: function(options) { + var chart = this, + renderer = chart.renderer, + btnOptions = merge(chart.options.navigation.buttonOptions, options), + onclick = btnOptions.onclick, + menuItems = btnOptions.menuItems, + symbol, + button, + symbolSize = btnOptions.symbolSize || 12; + if (!chart.btnCount) { + chart.btnCount = 0; + } - chart.exportSVGElements.push(button, symbol); + // Keeps references to the button elements + if (!chart.exportDivElements) { + chart.exportDivElements = []; + chart.exportSVGElements = []; + } - }, + if (btnOptions.enabled === false) { + return; + } - /** - * Destroy the buttons. - */ - destroyExport: function (e) { - var chart = e.target, - i, - elem; - // Destroy the extra buttons added - for (i = 0; i < chart.exportSVGElements.length; i++) { - elem = chart.exportSVGElements[i]; + var attr = btnOptions.theme, + states = attr.states, + hover = states && states.hover, + select = states && states.select, + callback; - // Destroy and null the svg/vml elements - if (elem) { // #1822 - elem.onclick = elem.ontouchstart = null; - chart.exportSVGElements[i] = elem.destroy(); - } - } + delete attr.states; - // Destroy the divs for the menu - for (i = 0; i < chart.exportDivElements.length; i++) { - elem = chart.exportDivElements[i]; + if (onclick) { + callback = function(e) { + e.stopPropagation(); + onclick.call(chart, e); + }; - // Remove the event handler - removeEvent(elem, 'mouseleave'); + } else if (menuItems) { + callback = function() { + chart.contextMenu( + button.menuClassName, + menuItems, + button.translateX, + button.translateY, + button.width, + button.height, + button + ); + button.setState(2); + }; + } - // Remove inline events - chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null; - // Destroy the div by moving to garbage bin - discardElement(elem); - } - } -}); + if (btnOptions.text && btnOptions.symbol) { + attr.paddingLeft = pick(attr.paddingLeft, 25); + } else if (!btnOptions.text) { + extend(attr, { + width: btnOptions.width, + height: btnOptions.height, + padding: 0 + }); + } -symbols.menu = function (x, y, width, height) { - var arr = [ - M, x, y + 2.5, - L, x + width, y + 2.5, - M, x, y + height / 2 + 0.5, - L, x + width, y + height / 2 + 0.5, - M, x, y + height - 1.5, - L, x + width, y + height - 1.5 - ]; - return arr; -}; + button = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select) + .addClass(options.className) + .attr({ -// Add the buttons on chart load -Chart.prototype.callbacks.push(function (chart) { - var n, - exportingOptions = chart.options.exporting, - buttons = exportingOptions.buttons; + 'stroke-linecap': 'round', - buttonOffset = 0; + title: chart.options.lang[btnOptions._titleKey], + zIndex: 3 // #4955 + }); + button.menuClassName = options.menuClassName || 'highcharts-menu-' + chart.btnCount++; - if (exportingOptions.enabled !== false) { + if (btnOptions.symbol) { + symbol = renderer.symbol( + btnOptions.symbol, + btnOptions.symbolX - (symbolSize / 2), + btnOptions.symbolY - (symbolSize / 2), + symbolSize, + symbolSize + ) + .addClass('highcharts-button-symbol') + .attr({ + zIndex: 1 + }).add(button); - for (n in buttons) { - chart.addButton(buttons[n]); - } - // Destroy the export elements at chart destroy - addEvent(chart, 'destroy', chart.destroyExport); - } + symbol.attr({ + stroke: btnOptions.symbolStroke, + fill: btnOptions.symbolFill, + 'stroke-width': btnOptions.symbolStrokeWidth || 1 + }); -}); + } + button.add() + .align(extend(btnOptions, { + width: button.width, + x: pick(btnOptions.x, chart.buttonOffset) // #1654 + }), true, 'spacingBox'); + chart.buttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1); + + chart.exportSVGElements.push(button, symbol); + + }, + + /** + * Destroy the buttons. + */ + destroyExport: function(e) { + var chart = e ? e.target : this, + exportSVGElements = chart.exportSVGElements, + exportDivElements = chart.exportDivElements; + + // Destroy the extra buttons added + if (exportSVGElements) { + each(exportSVGElements, function(elem, i) { + + // Destroy and null the svg/vml elements + if (elem) { // #1822 + elem.onclick = elem.ontouchstart = null; + chart.exportSVGElements[i] = elem.destroy(); + } + }); + exportSVGElements.length = 0; + } + + // Destroy the divs for the menu + if (exportDivElements) { + each(exportDivElements, function(elem, i) { + + // Remove the event handler + removeEvent(elem, 'mouseleave'); + + // Remove inline events + chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null; + + // Destroy the div by moving to garbage bin + discardElement(elem); + }); + exportDivElements.length = 0; + } + } + }); + + + + + symbols.menu = function(x, y, width, height) { + var arr = [ + 'M', x, y + 2.5, + 'L', x + width, y + 2.5, + 'M', x, y + height / 2 + 0.5, + 'L', x + width, y + height / 2 + 0.5, + 'M', x, y + height - 1.5, + 'L', x + width, y + height - 1.5 + ]; + return arr; + }; + + // Add the buttons on chart load + Chart.prototype.renderExporting = function() { + var n, + exportingOptions = this.options.exporting, + buttons = exportingOptions.buttons, + isDirty = this.isDirtyExporting || !this.exportSVGElements; + + this.buttonOffset = 0; + if (this.isDirtyExporting) { + this.destroyExport(); + } + + if (isDirty && exportingOptions.enabled !== false) { + + for (n in buttons) { + this.addButton(buttons[n]); + } + + this.isDirtyExporting = false; + } + + // Destroy the export elements at chart destroy + addEvent(this, 'destroy', this.destroyExport); + }; + + Chart.prototype.callbacks.push(function(chart) { + + function update(prop, options, redraw) { + chart.isDirtyExporting = true; + merge(true, chart.options[prop], options); + if (pick(redraw, true)) { + chart.redraw(); + } + + } + + chart.renderExporting(); + + addEvent(chart, 'redraw', chart.renderExporting); + + // Add update methods to handle chart.update and chart.exporting.update + // and chart.navigation.update. + each(['exporting', 'navigation'], function(prop) { + chart[prop] = { + update: function(options, redraw) { + update(prop, options, redraw); + } + }; + }); + }); + + }(Highcharts)); }));