Blame | Letzte Änderung | Log anzeigen | RSS feed
/*** @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: 'highstock.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 = '<?xml version=\"1.0" standalone=\"no\"?><!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">',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 filetry {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 precedencescale 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 settingclipwidth = clipwidth * dpiCorrection;clipheight = clipheight * dpiCorrection;// redefine the viewportpage.viewportSize = { width: clipwidth, height: clipheight};// make the paper a bit larger than the viewportpage.paperSize = { width: clipwidth + 2 , height: clipheight + 2 };}};exit = function (result) {if (serverMode) {//Calling page.close(), may stop the increasing heap allocationpage.close();}exitCallback(result);};convert = function (svg) {var base64;scaleAndClipPage(svg);if (outType === 'pdf' || output !== undefined || !serverMode) {if (output === undefined) {// in case of pdf filesoutput = 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 waitingimagesLoaded = true;}try {if (outType.toLowerCase() === 'svg') {// output svgsvg = svg.html.replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ').replace(/ href=/g, ' xlink:href=').replace(/<\/svg>.*?$/, '</svg>');// add xml doc typesvg = SVG_DOCTYPE + svg;if (output !== undefined) {// write the filesvgFile = fs.open(output, "w");svgFile.write(svg);svgFile.close();exit(output);} else {// return the svg as a stringexit(svg);}} else {// output binary images or pdfif (!imagesLoaded) {// render with interval, waiting for all images loadedinterval = 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 rightawayconvert(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 insertionfunction loadScript(varStr, codeStr) {var $script = $('<script>').attr('type', 'text/javascript');$script.html('var ' + varStr + ' = ' + codeStr);document.getElementsByTagName("head")[0].appendChild($script[0]);if (window[varStr] !== undefined) {console.log('Highcharts.' + varStr + '.parsed');}}function parseData(completeHandler, chartOptions, dataConfig) {try {dataConfig.complete = completeHandler;Highcharts.data(dataConfig, chartOptions);} catch (error) {completeHandler(undefined);}}if (input !== 'undefined') {loadScript('options', input);}if (callback !== 'undefined') {loadScript('cb', callback);}if (globalOptionsArg !== 'undefined') {loadScript('globalOptions', globalOptionsArg);}if (dataOptionsArg !== 'undefined') {loadScript('dataOptions', dataOptionsArg);}if (customCodeArg !== 'undefined') {loadScript('customCode', customCodeArg);}$(document.body).css('margin', '0px');if (outputType === 'jpeg') {$(document.body).css('backgroundColor', 'white');}$container = $('<div>').appendTo(document.body);$container.attr('id', 'container');// disable animationsHighcharts.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 globalOptionsif (globalOptions) {Highcharts.setOptions(globalOptions);}// Load dataif (dataOptions) {parseData(function completeHandler(opts) {// Merge series configsif (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 customCodeif (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() === "<svg" || input.substring(0, 5).toLowerCase() === "<?xml"|| input.substring(0, 9).toLowerCase() === "<!doctype") {//render page directly from svg filesvg = page.evaluate(loadChart, input, outType);page.viewportSize = { width: svg.width, height: svg.height };renderSVG(svg);} else {/*** We have a js file, let's render serverside from Highcharts options and grab the svg from it*/// load our javascript dependencies based on the constructorif (constr === 'Map') {jsFiles = config.files.highmaps;} else if (constr === 'StockChart')jsFiles = config.files.highstock;else {jsFiles = config.files.highcharts;}// load necessary librariesfor (jsFile in jsFiles) {if (jsFiles.hasOwnProperty(jsFile)) {page.injectJs(jsFiles[jsFile]);}}// load chart in page and return svg height and widthsvg = page.evaluate(createChart, constr, input, globalOptions, dataOptions, customCode, outType, callback, messages);if (!window.optionsParsed) {exit('ERROR: the options variable was not available or couldn\'t be parsed, does the infile contain an syntax error? Input used:' + input);}if (callback !== undefined && !window.callbackParsed) {exit('ERROR: the callback variable was not available, does the callback contain an syntax error? Callback used: ' + callback);}renderSVG(svg);}});}};startServer = function (host, port) {var server = require('webserver').create();server.listen(host + ':' + port,function (request, response) {var jsonStr = request.postRaw || request.post,params,msg;try {params = JSON.parse(jsonStr);if (params.status) {// for server health validationresponse.statusCode = 200;response.write('OK');response.close();} else {render(params, function (result) {response.statusCode = 200;response.write(result);response.close();});}} catch (e) {msg = "Failed rendering: \n" + e;response.statusCode = 500;response.setHeader('Content-Type', 'text/plain');response.setHeader('Content-Length', msg.length);response.write(msg);response.close();}}); // end server.listen// switch to serverModeserverMode = true;console.log("OK, PhantomJS is ready.");};args = mapCLArguments();// set tmpDir, for output temporary files.if (args.tmpdir === undefined) {config.tmpDir = fs.workingDirectory + '/tmp';} else {config.tmpDir = args.tmpdir;}// exists tmpDir and is it writable?if (!fs.exists(config.tmpDir)) {try{fs.makeDirectory(config.tmpDir);} catch (e) {console.log('ERROR: Cannot create temp directory for ' + config.tmpDir);}}if (args.host !== undefined && args.port !== undefined) {startServer(args.host, args.port);} else {// presume commandline usagerender(args, function (msg) {console.log(msg);phantom.exit();});}}());