/** * @license Highcharts JS v3.0.1 (2012-11-02) * * (c) 20013-2014 * * Author: Gert Vaartjes * * License: www.highcharts.com/license * * version: 2.0.1 */ /*jslint white: true */ /*global window, require, phantom, console, $, document, Image, Highcharts, clearTimeout, clearInterval, options, cb, globalOptions, dataOptions, customCode */ (function () { "use strict"; var config = { /* define locations of mandatory javascript files. * Depending on purchased license change the HIGHCHARTS property to * highcharts.js or highstock.js */ files: { highcharts: { JQUERY: 'jquery.1.9.1.min.js', HIGHCHARTS: 'highcharts.js', HIGHCHARTS_MORE: 'highcharts-more.js', HIGHCHARTS_DATA: 'data.js', HIGHCHARTS_DRILLDOWN: 'drilldown.js', HIGHCHARTS_FUNNEL: 'funnel.js', HIGHCHARTS_HEATMAP: 'heatmap.js', HIGHCHARTS_TREEMAP: 'treemap.js', HIGHCHARTS_3D: 'highcharts-3d.js', HIGHCHARTS_NODATA: 'no-data-to-display.js', // Uncomment below if you have both Highcharts and Highmaps license // HIGHCHARTS_MAP: 'map.js', HIGHCHARTS_SOLID_GAUGE: 'solid-gauge.js', BROKEN_AXIS: 'broken-axis.js' }, highstock: { JQUERY: 'jquery.1.9.1.min.js', HIGHCHARTS: 'highcharts.js', HIGHCHARTS_MORE: 'highcharts-more.js', HIGHCHARTS_DATA: 'data.js', HIGHCHARTS_DRILLDOWN: 'drilldown.js', HIGHCHARTS_FUNNEL: 'funnel.js', HIGHCHARTS_HEATMAP: 'heatmap.js', HIGHCHARTS_TREEMAP: 'treemap.js', HIGHCHARTS_3D: 'highcharts-3d.js', HIGHCHARTS_NODATA: 'no-data-to-display.js', // Uncomment below if you have both Highstock and Highmaps license // HIGHCHARTS_MAP: 'map.js', HIGHCHARTS_SOLID_GAUGE: 'solid-gauge.js', BROKEN_AXIS: 'broken-axis.js' }, highmaps: { JQUERY: 'jquery.1.9.1.min.js', HIGHCHARTS: 'highmaps.js', HIGHCHARTS_DATA: 'data.js', HIGHCHARTS_DRILLDOWN: 'drilldown.js', HIGHCHARTS_HEATMAP: 'heatmap.js', HIGHCHARTS_NODATA: 'no-data-to-display.js' } }, TIMEOUT: 5000 /* 5 seconds timout for loading images */ }, mapCLArguments, render, startServer = false, args, pick, SVG_DOCTYPE = '', dpiCorrection = 1.4, system = require('system'), fs = require('fs'), serverMode = false; pick = function () { var args = arguments, i, arg, length = args.length; for (i = 0; i < length; i += 1) { arg = args[i]; if (arg !== undefined && arg !== null && arg !== 'null' && arg != '0') { return arg; } } }; mapCLArguments = function () { var map = {}, i, key; if (system.args.length < 1) { console.log('Commandline Usage: highcharts-convert.js -infile URL -outfile filename -scale 2.5 -width 300 -constr Chart -callback callback.js'); console.log(', or run PhantomJS as server: highcharts-convert.js -host 127.0.0.1 -port 1234'); } for (i = 0; i < system.args.length; i += 1) { if (system.args[i].charAt(0) === '-') { key = system.args[i].substr(1, i.length); if (key === 'infile' || key === 'callback' || key === 'dataoptions' || key === 'globaloptions' || key === 'customcode') { // get string from file try { map[key] = fs.read(system.args[i + 1]).replace(/^\s+/, ''); } catch (e) { console.log('Error: cannot find file, ' + system.args[i + 1]); phantom.exit(); } } else { map[key] = system.args[i + 1]; } } } return map; }; render = function (params, exitCallback) { var page = require('webpage').create(), messages = {}, scaleAndClipPage, loadChart, createChart, input, constr, callback, width, output, outType, timer, renderSVG, convert, exit, interval, counter, imagesLoaded = false; messages.optionsParsed = 'Highcharts.options.parsed'; messages.callbackParsed = 'Highcharts.cb.parsed'; window.optionsParsed = false; window.callbackParsed = false; page.onConsoleMessage = function (msg) { console.log(msg); /* * Ugly hack, but only way to get messages out of the 'page.evaluate()' * sandbox. If any, please contribute with improvements on this! */ /* to check options or callback are properly parsed */ if (msg === messages.optionsParsed) { window.optionsParsed = true; } if (msg === messages.callbackParsed) { window.callbackParsed = true; } }; page.onAlert = function (msg) { console.log(msg); }; /* scale and clip the page */ scaleAndClipPage = function (svg) { /* param: svg: The scg configuration object */ var zoom = 1, pageWidth = pick(params.width, svg.width), clipwidth, clipheight; if (parseInt(pageWidth, 10) == pageWidth) { zoom = pageWidth / svg.width; } /* set this line when scale factor has a higher precedence scale has precedence : page.zoomFactor = params.scale ? zoom * params.scale : zoom;*/ /* params.width has a higher precedence over scaling, to not break backover compatibility */ page.zoomFactor = params.scale && params.width == undefined ? zoom * params.scale : zoom; clipwidth = svg.width * page.zoomFactor; clipheight = svg.height * page.zoomFactor; /* define the clip-rectangle */ /* ignored for PDF, see https://github.com/ariya/phantomjs/issues/10465 */ page.clipRect = { top: 0, left: 0, width: clipwidth, height: clipheight }; /* for pdf we need a bit more paperspace in some cases for example (w:600,h:400), I don't know why.*/ if (outType === 'pdf') { // changed to a multiplication with 1.333 to correct systems dpi setting clipwidth = clipwidth * dpiCorrection; clipheight = clipheight * dpiCorrection; // redefine the viewport page.viewportSize = { width: clipwidth, height: clipheight}; // make the paper a bit larger than the viewport page.paperSize = { width: clipwidth + 2 , height: clipheight + 2 }; } }; exit = function (result) { if (serverMode) { //Calling page.close(), may stop the increasing heap allocation page.close(); } exitCallback(result); }; convert = function (svg) { var base64; scaleAndClipPage(svg); if (outType === 'pdf' || output !== undefined || !serverMode) { if (output === undefined) { // in case of pdf files output = config.tmpDir + '/chart.' + outType; } page.render(output); exit(output); } else { base64 = page.renderBase64(outType); exit(base64); } }; function decrementImgCounter() { counter -= 1; if (counter < 1) { imagesLoaded = true; } } function loadImages(imgUrls) { var i, img; counter = imgUrls.length; for (i = 0; i < imgUrls.length; i += 1) { img = new Image(); /* onload decrements the counter, also when error (perhaps 404), don't wait for this image to be loaded */ img.onload = img.onerror = decrementImgCounter; /* force loading of images by setting the src attr.*/ img.src = imgUrls[i]; } } renderSVG = function (svg) { var svgFile; // From this point we have 'loaded' or 'created' a SVG // Do we have to load images? if (svg.imgUrls.length > 0) { loadImages(svg.imgUrls); } else { // no images present, no loading, no waiting imagesLoaded = true; } try { if (outType.toLowerCase() === 'svg') { // output svg svg = svg.html.replace(/'); // add xml doc type svg = SVG_DOCTYPE + svg; if (output !== undefined) { // write the file svgFile = fs.open(output, "w"); svgFile.write(svg); svgFile.close(); exit(output); } else { // return the svg as a string exit(svg); } } else { // output binary images or pdf if (!imagesLoaded) { // render with interval, waiting for all images loaded interval = window.setInterval(function () { if (imagesLoaded) { clearTimeout(timer); clearInterval(interval); convert(svg); } }, 50); // we have a 5 second timeframe.. timer = window.setTimeout(function () { clearInterval(interval); exitCallback('ERROR: While rendering, there\'s is a timeout reached'); }, config.TIMEOUT); } else { // images are loaded, render rightaway convert(svg); } } } catch (e) { console.log('ERROR: While rendering, ' + e); } }; loadChart = function (input, outputType) { var nodeIter, nodes, elem, opacity, svgElem, imgs, imgUrls, imgIndex; document.body.style.margin = '0px'; document.body.innerHTML = input; if (outputType === 'jpeg') { document.body.style.backgroundColor = 'white'; } nodes = document.querySelectorAll('*[stroke-opacity]'); for (nodeIter = 0; nodeIter < nodes.length; nodeIter += 1) { elem = nodes[nodeIter]; opacity = elem.getAttribute('stroke-opacity'); elem.removeAttribute('stroke-opacity'); elem.setAttribute('opacity', opacity); } svgElem = document.getElementsByTagName('svg')[0]; imgs = document.getElementsByTagName('image'); imgUrls = []; for (imgIndex = 0; imgIndex < imgs.length; imgIndex = imgIndex + 1) { imgUrls.push(imgs[imgIndex].href.baseVal); } return { html: document.body.innerHTML, width: svgElem.getAttribute("width"), height: svgElem.getAttribute("height"), imgUrls: imgUrls }; }; createChart = function (constr, input, globalOptionsArg, dataOptionsArg, customCodeArg, outputType, callback, messages) { var $container, chart, nodes, nodeIter, elem, opacity, imgIndex, imgs, imgUrls; // dynamic script insertion function loadScript(varStr, codeStr) { var $script = $('
').appendTo(document.body); $container.attr('id', 'container'); // disable animations Highcharts.SVGRenderer.prototype.Element.prototype.animate = Highcharts.SVGRenderer.prototype.Element.prototype.attr; Highcharts.setOptions({ plotOptions: { series: { animation: false } } }); if (!options.chart) { options.chart = {}; } options.chart.renderTo = $container[0]; // check if witdh is set. Order of precedence: // args.width, options.chart.width and 600px // OLD. options.chart.width = width || options.chart.width || 600; // Notice we don't use commandline parameter width here. Commandline parameter width is used for scaling. options.chart.width = (options.exporting && options.exporting.sourceWidth) || options.chart.width || 600; options.chart.height = (options.exporting && options.exporting.sourceHeight) || options.chart.height || 400; // Load globalOptions if (globalOptions) { Highcharts.setOptions(globalOptions); } // Load data if (dataOptions) { parseData(function completeHandler(opts) { // Merge series configs if (options.series) { Highcharts.each(options.series, function (series, i) { options.series[i] = Highcharts.merge(series, opts.series[i]); }); } var mergedOptions = Highcharts.merge(opts, options); // Run customCode if (customCode) { customCode(mergedOptions); } chart = new Highcharts[constr](mergedOptions, cb); }, options, dataOptions); } else { chart = new Highcharts[constr](options, cb); } /* remove stroke-opacity paths, used by mouse-trackers, they turn up as * as fully opaque in the PDF */ nodes = document.querySelectorAll('*[stroke-opacity]'); for (nodeIter = 0; nodeIter < nodes.length; nodeIter += 1) { elem = nodes[nodeIter]; opacity = elem.getAttribute('stroke-opacity'); elem.removeAttribute('stroke-opacity'); elem.setAttribute('opacity', opacity); } imgs = document.getElementsByTagName('image'); imgUrls = []; for (imgIndex = 0; imgIndex < imgs.length; imgIndex = imgIndex + 1) { imgUrls.push(imgs[imgIndex].href.baseVal); } return { html: $('div.highcharts-container')[0].innerHTML, width: chart.chartWidth, height: chart.chartHeight, imgUrls: imgUrls }; }; if (params.length < 1) { exit("Error: Insufficient parameters"); } else { input = params.infile; output = params.outfile; if (output !== undefined) { outType = pick(output.split('.').pop(),'png'); } else { outType = pick(params.type,'png'); } constr = pick(params.constr, 'Chart'); callback = params.callback; width = params.width; if (input === undefined || input.length === 0) { exit('Error: Insuficient or wrong parameters for rendering'); } page.open('about:blank', function (status) { var svg, globalOptions = params.globaloptions, dataOptions = params.dataoptions, customCode = 'function customCode(options) {\n' + params.customcode + '}\n', jsFile, jsFiles; /* Decide if we have to generate a svg first before rendering */ if (input.substring(0, 4).toLowerCase() === "