Subversion-Projekte lars-tiefland.cienc

Revision

Blame | Letzte Änderung | Log anzeigen | RSS feed

/**
 * @license Highstock JS v2.1.8 (2015-08-20)
 * Exporting module
 *
 * (c) 2010-2014 Torstein Honsi
 *
 * License: www.highcharts.com/license
 */

// JSLint options:
/*global Highcharts, HighchartsAdapter, document, window, Math, setTimeout */

(function (Highcharts) { // encapsulate

// create shortcuts
var Chart = Highcharts.Chart,
        addEvent = Highcharts.addEvent,
        removeEvent = Highcharts.removeEvent,
        fireEvent = HighchartsAdapter.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,
        doc = document,
        win = window,
        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;

        // 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 = {
        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'
        },

        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 the export related options
defaultOptions.exporting = {
        //enabled: true,
        //filename: 'chart',
        type: 'image/png',
        url: 'http://export.highcharts.com/',
        //width: undefined,
        //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
                        /*
                        ,{

                                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;

        // create the form
        form = createElement('form', merge({
                method: 'post',
                action: url,
                enctype: 'multipart/form-data'
        }, formAttributes), {
                display: NONE
        }, doc.body);

        // add the data
        for (name in data) {
                createElement('input', {
                        type: HIDDEN,
                        name: name,
                        value: data[name]
                }, null, form);
        }

        // submit
        form.submit();

        // clean up
        discardElement(form);
};

extend(Chart.prototype, {

        /**
         * 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\([^#]+#/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];
                        })*/

                        // Replace HTML entities, issue #347
                        .replace(/&nbsp;/g, '\u00A0') // no-break space
                        .replace(/&shy;/g,  '\u00AD') // soft hyphen

                        // 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();
                        });
        },

        /**
         * Return innerHTML of chart. Used as hook for plugins.
         */
        getChartHTML: function () {
                return this.container.innerHTML;
        },

        /**
         * 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; // docs: experimental, see #2473
                        

                // IE compatibility hack for generating SVG content that it doesn't really understand
                if (!doc.createElementNS) {
                        /*jslint unparam: true*//* allow unused parameter ns in function below */
                        doc.createElementNS = function (ns, tagName) {
                                return doc.createElement(tagName);
                        };
                        /*jslint unparam: false*/
                }

                // 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;

                // override some options
                extend(options.chart, {
                        animation: false,
                        renderTo: sandbox,
                        forExport: !allowHTML,
                        width: sourceWidth,
                        height: sourceHeight
                });
                options.exporting.enabled = false; // hide buttons in print
                delete options.data; // #3004

                // prepare for replicating the chart
                options.series = [];
                each(chart.series, function (serie) {
                        seriesOptions = merge(serie.options, {
                                animation: false, // turn off animation
                                enableMouseTracking: false,
                                showCheckbox: false,
                                visible: serie.visible
                        });

                        if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
                                options.series.push(seriesOptions);
                        }
                });

                // 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);

                // 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;

                                if (axisCopy && (userMin !== UNDEFINED || userMax !== UNDEFINED)) {
                                        axisCopy.setExtremes(userMin, userMax, true, false);
                                }
                        });
                });

                // get the SVG from the container's innerHTML
                svg = chartCopy.getChartHTML();

                // free up memory
                options = null;
                chartCopy.destroy();
                discardElement(sandbox);

                // 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>');
                        }
                }

                // sanitize
                svg = this.sanitizeSVG(svg);

                // 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;
        },

        getSVGForExport: function (options, chartOptions) {
                var chartExportingOptions = this.options.exporting;

                return this.getSVG(merge(
                        { chart: { borderRadius: 0 } },
                        chartExportingOptions.chartOptions,
                        chartOptions,
                        {
                                exporting: {
                                        sourceWidth: (options && options.sourceWidth) || chartExportingOptions.sourceWidth,
                                        sourceHeight: (options && options.sourceHeight) || chartExportingOptions.sourceHeight
                                }
                        }
                ));
        },

        /**
         * 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) {
                
                var svg = this.getSVGForExport(options, chartOptions);

                // merge the options
                options = merge(this.options.exporting, options);

                // 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 || 2,
                        svg: svg
                }, options.formAttributes);

        },

        /**
         * Print the chart
         */
        print: function () {

                var chart = this,
                        container = chart.container,
                        origDisplay = [],
                        origParent = container.parentNode,
                        body = doc.body,
                        childNodes = body.childNodes;

                if (chart.isPrinting) { // block the button while in printing mode
                        return;
                }

                chart.isPrinting = true;

                fireEvent(chart, 'beforePrint');

                // 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
                win.focus(); // #1510
                win.print();

                // allow the browser to prepare before reverting
                setTimeout(function () {

                        // put the chart back in
                        origParent.appendChild(container);

                        // restore all body content
                        each(childNodes, function (node, i) {
                                if (node.nodeType === 1) {
                                        node.style.display = origDisplay[i];
                                }
                        });

                        chart.isPrinting = false;

                        fireEvent(chart, 'afterPrint');

                }, 1000);

        },

        /**
         * 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();
                                }
                        };

                // create the menu only the first time
                if (!menu) {

                        // 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, null,
                                extend({
                                        MozBoxShadow: boxShadow,
                                        WebkitBoxShadow: boxShadow,
                                        boxShadow: boxShadow
                                }, navOptions.menuStyle), menu);

                        // hide on mouse out
                        hide = function () {
                                css(menu, { display: NONE });
                                if (button) {
                                        button.setState(0);
                                }
                                chart.openMenu = false;
                        };

                        // Hide the menu some time after mouse leave (#1357)
                        addEvent(menu, 'mouseleave', function () {
                                hideTimer = setTimeout(hide, 500);
                        });
                        addEvent(menu, 'mouseenter', function () {
                                clearTimeout(hideTimer);
                        });


                        // Hide it on clicking or touching outside the menu (#2258, #2335, #2407)
                        addEvent(document, 'mouseup', docMouseUpHandler);
                        addEvent(chart, 'destroy', function () {
                                removeEvent(document, 'mouseup', docMouseUpHandler);
                        });


                        // 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) {
                                                                e.stopPropagation();
                                                                hide();
                                                                if (item.onclick) {
                                                                        item.onclick.apply(chart, arguments);
                                                                }
                                                        },
                                                        innerHTML: item.text || chart.options.lang[item.textKey]
                                                }, extend({
                                                        cursor: 'pointer'
                                                }, menuItemStyle), innerMenu);


                                        // 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);

                        chart.exportMenuWidth = menu.offsetWidth;
                        chart.exportMenuHeight = menu.offsetHeight;
                }

                menuStyle = { display: 'block' };

                // 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;
                }

                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;
                }

                // Keeps references to the button elements
                if (!chart.exportDivElements) {
                        chart.exportDivElements = [];
                        chart.exportSVGElements = [];
                }

                if (btnOptions.enabled === false) {
                        return;
                }


                var attr = btnOptions.theme,
                        states = attr.states,
                        hover = states && states.hover,
                        select = states && states.select,
                        callback;

                delete attr.states;

                if (onclick) {
                        callback = function (e) {
                                e.stopPropagation();
                                onclick.call(chart, e);
                        };

                } else if (menuItems) {
                        callback = function () {
                                chart.contextMenu(
                                        button.menuClassName,
                                        menuItems,
                                        button.translateX,
                                        button.translateY,
                                        button.width,
                                        button.height,
                                        button
                                );
                                button.setState(2);
                        };
                }


                if (btnOptions.text && btnOptions.symbol) {
                        attr.paddingLeft = Highcharts.pick(attr.paddingLeft, 25);

                } else if (!btnOptions.text) {
                        extend(attr, {
                                width: btnOptions.width,
                                height: btnOptions.height,
                                padding: 0
                        });
                }

                button = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select)
                        .attr({
                                title: chart.options.lang[btnOptions._titleKey],
                                'stroke-linecap': 'round'
                        });
                button.menuClassName = options.menuClassName || PREFIX + 'menu-' + chart.btnCount++;

                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);
                }

                button.add()
                        .align(extend(btnOptions, {
                                width: button.width,
                                x: Highcharts.pick(btnOptions.x, buttonOffset) // #1654
                        }), true, 'spacingBox');

                buttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1);

                chart.exportSVGElements.push(button, symbol);

        },

        /**
         * 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];

                        // Destroy and null the svg/vml elements
                        if (elem) { // #1822
                                elem.onclick = elem.ontouchstart = null;
                                chart.exportSVGElements[i] = elem.destroy();
                        }
                }

                // Destroy the divs for the menu
                for (i = 0; i < chart.exportDivElements.length; i++) {
                        elem = chart.exportDivElements[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);
                }
        }
});


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.callbacks.push(function (chart) {
        var n,
                exportingOptions = chart.options.exporting,
                buttons = exportingOptions.buttons;

        buttonOffset = 0;

        if (exportingOptions.enabled !== false) {

                for (n in buttons) {
                        chart.addButton(buttons[n]);
                }

                // Destroy the export elements at chart destroy
                addEvent(chart, 'destroy', chart.destroyExport);
        }

});


}(Highcharts));