|
|
- /**
- * @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 = '<?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 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(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ').replace(/ href=/g, ' xlink:href=').replace(/<\/svg>.*?$/, '</svg>');
- // 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 = $('<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 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() === "<svg" || input.substring(0, 5).toLowerCase() === "<?xml"
- || input.substring(0, 9).toLowerCase() === "<!doctype") {
- //render page directly from svg file
- svg = 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 constructor
- if (constr === 'Map') {
- jsFiles = config.files.highmaps;
- } else if (constr === 'StockChart')
- jsFiles = config.files.highstock;
- else {
- jsFiles = config.files.highcharts;
- }
-
- // load necessary libraries
- for (jsFile in jsFiles) {
- if (jsFiles.hasOwnProperty(jsFile)) {
- page.injectJs(jsFiles[jsFile]);
- }
- }
-
- // load chart in page and return svg height and width
- svg = 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 validation
- response.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 serverMode
- serverMode = 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 usage
- render(args, function (msg) {
- console.log(msg);
- phantom.exit();
- });
- }
- }());
|