市长热线演示版

highcharts-convert.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. /**
  2. * @license Highcharts JS v3.0.1 (2012-11-02)
  3. *
  4. * (c) 20013-2014
  5. *
  6. * Author: Gert Vaartjes
  7. *
  8. * License: www.highcharts.com/license
  9. *
  10. * version: 2.0.1
  11. */
  12. /*jslint white: true */
  13. /*global window, require, phantom, console, $, document, Image, Highcharts, clearTimeout, clearInterval, options, cb, globalOptions, dataOptions, customCode */
  14. (function () {
  15. "use strict";
  16. var config = {
  17. /* define locations of mandatory javascript files */
  18. HIGHCHARTS: 'highstock.js',
  19. HIGHCHARTS_MORE: 'highcharts-more.js',
  20. HIGHCHARTS_DATA: 'data.js',
  21. JQUERY: 'jquery.1.9.1.min.js',
  22. TIMEOUT: 2000 /* 2 seconds timout for loading images */
  23. },
  24. mapCLArguments,
  25. render,
  26. startServer = false,
  27. args,
  28. pick,
  29. 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\">',
  30. dpiCorrection = 1.4,
  31. system = require('system'),
  32. fs = require('fs'),
  33. serverMode = false;
  34. pick = function () {
  35. var args = arguments, i, arg, length = args.length;
  36. for (i = 0; i < length; i += 1) {
  37. arg = args[i];
  38. if (arg !== undefined && arg !== null && arg !== 'null' && arg != '0') {
  39. return arg;
  40. }
  41. }
  42. };
  43. mapCLArguments = function () {
  44. var map = {},
  45. i,
  46. key;
  47. if (system.args.length < 1) {
  48. console.log('Commandline Usage: highcharts-convert.js -infile URL -outfile filename -scale 2.5 -width 300 -constr Chart -callback callback.js');
  49. console.log(', or run PhantomJS as server: highcharts-convert.js -host 127.0.0.1 -port 1234');
  50. }
  51. for (i = 0; i < system.args.length; i += 1) {
  52. if (system.args[i].charAt(0) === '-') {
  53. key = system.args[i].substr(1, i.length);
  54. if (key === 'infile' || key === 'callback' || key === 'dataoptions' || key === 'globaloptions' || key === 'customcode') {
  55. // get string from file
  56. try {
  57. map[key] = fs.read(system.args[i + 1]).replace(/^\s+/, '');
  58. } catch (e) {
  59. console.log('Error: cannot find file, ' + system.args[i + 1]);
  60. phantom.exit();
  61. }
  62. } else {
  63. map[key] = system.args[i + 1];
  64. }
  65. }
  66. }
  67. return map;
  68. };
  69. render = function (params, exitCallback) {
  70. var page = require('webpage').create(),
  71. messages = {},
  72. scaleAndClipPage,
  73. loadChart,
  74. createChart,
  75. input,
  76. constr,
  77. callback,
  78. width,
  79. output,
  80. outType,
  81. timer,
  82. renderSVG,
  83. convert,
  84. exit,
  85. interval;
  86. messages.imagesLoaded = 'Highcharts.images.loaded';
  87. messages.optionsParsed = 'Highcharts.options.parsed';
  88. messages.callbackParsed = 'Highcharts.cb.parsed';
  89. window.imagesLoaded = false;
  90. window.optionsParsed = false;
  91. window.callbackParsed = false;
  92. page.onConsoleMessage = function (msg) {
  93. //console.log(msg);
  94. /*
  95. * Ugly hack, but only way to get messages out of the 'page.evaluate()'
  96. * sandbox. If any, please contribute with improvements on this!
  97. */
  98. if (msg === messages.imagesLoaded) {
  99. window.imagesLoaded = true;
  100. }
  101. /* more ugly hacks, to check options or callback are properly parsed */
  102. if (msg === messages.optionsParsed) {
  103. window.optionsParsed = true;
  104. }
  105. if (msg === messages.callbackParsed) {
  106. window.callbackParsed = true;
  107. }
  108. };
  109. page.onAlert = function (msg) {
  110. console.log(msg);
  111. };
  112. /* scale and clip the page */
  113. scaleAndClipPage = function (svg) {
  114. /* param: svg: The scg configuration object
  115. */
  116. var zoom = 1,
  117. pageWidth = pick(params.width, svg.width),
  118. clipwidth,
  119. clipheight;
  120. if (parseInt(pageWidth, 10) == pageWidth) {
  121. zoom = pageWidth / svg.width;
  122. }
  123. /* set this line when scale factor has a higher precedence
  124. scale has precedence : page.zoomFactor = params.scale ? zoom * params.scale : zoom;*/
  125. /* params.width has a higher precedence over scaling, to not break backover compatibility */
  126. page.zoomFactor = params.scale && params.width == undefined ? zoom * params.scale : zoom;
  127. clipwidth = svg.width * page.zoomFactor;
  128. clipheight = svg.height * page.zoomFactor;
  129. /* define the clip-rectangle */
  130. /* ignored for PDF, see https://github.com/ariya/phantomjs/issues/10465 */
  131. page.clipRect = {
  132. top: 0,
  133. left: 0,
  134. width: clipwidth,
  135. height: clipheight
  136. };
  137. /* for pdf we need a bit more paperspace in some cases for example (w:600,h:400), I don't know why.*/
  138. if (outType === 'pdf') {
  139. // changed to a multiplication with 1.333 to correct systems dpi setting
  140. clipwidth = clipwidth * dpiCorrection;
  141. clipheight = clipheight * dpiCorrection;
  142. // redefine the viewport
  143. page.viewportSize = { width: clipwidth, height: clipheight};
  144. // make the paper a bit larger than the viewport
  145. page.paperSize = { width: clipwidth + 2 , height: clipheight + 2 };
  146. }
  147. };
  148. exit = function (result) {
  149. if (serverMode) {
  150. //Calling page.close(), may stop the increasing heap allocation
  151. page.close();
  152. }
  153. exitCallback(result);
  154. };
  155. convert = function (svg) {
  156. var base64;
  157. scaleAndClipPage(svg);
  158. if (outType === 'pdf' || output !== undefined || !serverMode) {
  159. if (output === undefined) {
  160. // in case of pdf files
  161. output = config.tmpDir + '/chart.' + outType;
  162. }
  163. page.render(output);
  164. exit(output);
  165. } else {
  166. base64 = page.renderBase64(outType);
  167. exit(base64);
  168. }
  169. };
  170. renderSVG = function (svg) {
  171. var svgFile;
  172. // From this point we have loaded/or created a SVG
  173. try {
  174. if (outType.toLowerCase() === 'svg') {
  175. // output svg
  176. svg = svg.html.replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ').replace(/ href=/g, ' xlink:href=').replace(/<\/svg>.*?$/, '</svg>');
  177. // add xml doc type
  178. svg = SVG_DOCTYPE + svg;
  179. if (output !== undefined) {
  180. // write the file
  181. svgFile = fs.open(output, "w");
  182. svgFile.write(svg);
  183. svgFile.close();
  184. exit(output);
  185. } else {
  186. // return the svg as a string
  187. exit(svg);
  188. }
  189. } else {
  190. // output binary images or pdf
  191. if (!window.imagesLoaded) {
  192. // render with interval, waiting for all images loaded
  193. interval = window.setInterval(function () {
  194. console.log('waiting');
  195. if (window.imagesLoaded) {
  196. clearTimeout(timer);
  197. clearInterval(interval);
  198. convert(svg);
  199. }
  200. }, 50);
  201. // we have a 3 second timeframe..
  202. timer = window.setTimeout(function () {
  203. clearInterval(interval);
  204. exitCallback('ERROR: While rendering, there\'s is a timeout reached');
  205. }, config.TIMEOUT);
  206. } else {
  207. // images are loaded, render rightaway
  208. convert(svg);
  209. }
  210. }
  211. } catch (e) {
  212. console.log('ERROR: While rendering, ' + e);
  213. }
  214. };
  215. loadChart = function (input, outputType, messages) {
  216. var nodeIter, nodes, elem, opacity, counter, svgElem;
  217. document.body.style.margin = '0px';
  218. document.body.innerHTML = input;
  219. function decrementImgCounter() {
  220. counter -= 1;
  221. if (counter < 1) {
  222. console.log(messages.imagesLoaded);
  223. }
  224. }
  225. function loadImages() {
  226. var images = document.getElementsByTagName('image'), i, img;
  227. if (images.length > 0) {
  228. counter = images.length;
  229. for (i = 0; i < images.length; i += 1) {
  230. img = new Image();
  231. /* onload decremnts the counter, also when error (perhaps 404), then we wont wait for this image to be loaded */
  232. img.onload = img.onerror = decrementImgCounter;
  233. /* force loading of images by setting the src attr.*/
  234. img.src = images[i].href.baseVal;
  235. }
  236. } else {
  237. // no images set property to:imagesLoaded = true
  238. console.log(messages.imagesLoaded);
  239. }
  240. }
  241. if (outputType === 'jpeg') {
  242. document.body.style.backgroundColor = 'white';
  243. }
  244. nodes = document.querySelectorAll('*[stroke-opacity]');
  245. for (nodeIter = 0; nodeIter < nodes.length; nodeIter += 1) {
  246. elem = nodes[nodeIter];
  247. opacity = elem.getAttribute('stroke-opacity');
  248. elem.removeAttribute('stroke-opacity');
  249. elem.setAttribute('opacity', opacity);
  250. }
  251. // ensure all image are loaded
  252. loadImages();
  253. svgElem = document.getElementsByTagName('svg')[0];
  254. return {
  255. html: document.body.innerHTML,
  256. width: svgElem.getAttribute("width"),
  257. height: svgElem.getAttribute("height")
  258. };
  259. };
  260. createChart = function (width, constr, input, globalOptionsArg, dataOptionsArg, customCodeArg, outputType, callback, messages) {
  261. var $container, chart, nodes, nodeIter, elem, opacity, counter;
  262. // dynamic script insertion
  263. function loadScript(varStr, codeStr) {
  264. var $script = $('<script>').attr('type', 'text/javascript');
  265. $script.html('var ' + varStr + ' = ' + codeStr);
  266. document.getElementsByTagName("head")[0].appendChild($script[0]);
  267. if (window[varStr] !== undefined) {
  268. console.log('Highcharts.' + varStr + '.parsed');
  269. }
  270. }
  271. function decrementImgCounter() {
  272. counter -= 1;
  273. if (counter < 1) {
  274. console.log(messages.imagesLoaded);
  275. }
  276. }
  277. function loadImages() {
  278. var $images = $('svg image'), i, img;
  279. if ($images.length > 0) {
  280. counter = $images.length;
  281. for (i = 0; i < $images.length; i += 1) {
  282. img = new Image();
  283. /* onload decremnts the counter, also when error (perhaps 404), then we wont wait for this image to be loaded */
  284. img.onload = img.onerror = decrementImgCounter;
  285. /* force loading of images by setting the src attr.*/
  286. img.src = $images[i].getAttribute('href');
  287. }
  288. } else {
  289. // no images set property to:imagesLoaded = true
  290. console.log(messages.imagesLoaded);
  291. }
  292. }
  293. function parseData(completeHandler, chartOptions, dataConfig) {
  294. try {
  295. dataConfig.complete = completeHandler;
  296. Highcharts.data(dataConfig, chartOptions);
  297. } catch (error) {
  298. completeHandler(undefined);
  299. }
  300. }
  301. if (input !== 'undefined') {
  302. loadScript('options', input);
  303. }
  304. if (callback !== 'undefined') {
  305. loadScript('cb', callback);
  306. }
  307. if (globalOptionsArg !== 'undefined') {
  308. loadScript('globalOptions', globalOptionsArg);
  309. }
  310. if (dataOptionsArg !== 'undefined') {
  311. loadScript('dataOptions', dataOptionsArg);
  312. }
  313. if (customCodeArg !== 'undefined') {
  314. loadScript('customCode', customCodeArg);
  315. }
  316. $(document.body).css('margin', '0px');
  317. if (outputType === 'jpeg') {
  318. $(document.body).css('backgroundColor', 'white');
  319. }
  320. $container = $('<div>').appendTo(document.body);
  321. $container.attr('id', 'container');
  322. // disable animations
  323. Highcharts.SVGRenderer.prototype.Element.prototype.animate = Highcharts.SVGRenderer.prototype.Element.prototype.attr;
  324. if (!options.chart) {
  325. options.chart = {};
  326. }
  327. options.chart.renderTo = $container[0];
  328. // check if witdh is set. Order of precedence:
  329. // args.width, options.chart.width and 600px
  330. // OLD. options.chart.width = width || options.chart.width || 600;
  331. // Notice we don't use commandline parameter width here. Commandline parameter width is used for scaling.
  332. options.chart.width = (options.exporting && options.exporting.sourceWidth) || options.chart.width || 600;
  333. options.chart.height = (options.exporting && options.exporting.sourceHeight) || options.chart.height || 400;
  334. // Load globalOptions
  335. if (globalOptions) {
  336. Highcharts.setOptions(globalOptions);
  337. }
  338. // Load data
  339. if (dataOptions) {
  340. parseData(function completeHandler(opts) {
  341. // Merge series configs
  342. if (options.series) {
  343. Highcharts.each(options.series, function (series, i) {
  344. options.series[i] = Highcharts.merge(series, opts.series[i]);
  345. });
  346. }
  347. var mergedOptions = Highcharts.merge(opts, options);
  348. // Run customCode
  349. if (customCode) {
  350. customCode(mergedOptions);
  351. }
  352. chart = new Highcharts[constr](mergedOptions, cb);
  353. // ensure images are all loaded
  354. loadImages();
  355. }, options, dataOptions);
  356. } else {
  357. chart = new Highcharts[constr](options, cb);
  358. // ensure images are all loaded
  359. loadImages();
  360. }
  361. /* remove stroke-opacity paths, used by mouse-trackers, they turn up as
  362. * as fully opaque in the PDF
  363. */
  364. nodes = document.querySelectorAll('*[stroke-opacity]');
  365. for (nodeIter = 0; nodeIter < nodes.length; nodeIter += 1) {
  366. elem = nodes[nodeIter];
  367. opacity = elem.getAttribute('stroke-opacity');
  368. elem.removeAttribute('stroke-opacity');
  369. elem.setAttribute('opacity', opacity);
  370. }
  371. return {
  372. //html: $container[0].firstChild.innerHTML,
  373. html: $('div.highcharts-container')[0].innerHTML,
  374. width: chart.chartWidth,
  375. height: chart.chartHeight
  376. };
  377. };
  378. if (params.length < 1) {
  379. exit("Error: Insuficient parameters");
  380. } else {
  381. input = params.infile;
  382. output = params.outfile;
  383. if (output !== undefined) {
  384. outType = pick(output.split('.').pop(),'png');
  385. } else {
  386. outType = pick(params.type,'png');
  387. }
  388. constr = pick(params.constr, 'Chart');
  389. callback = params.callback;
  390. width = params.width;
  391. if (input === undefined || input.length === 0) {
  392. exit('Error: Insuficient or wrong parameters for rendering');
  393. }
  394. page.open('about:blank', function (status) {
  395. var svg,
  396. globalOptions = params.globaloptions,
  397. dataOptions = params.dataoptions,
  398. customCode = 'function customCode(options) {\n' + params.customcode + '}\n';
  399. /* Decide if we have to generate a svg first before rendering */
  400. if (input.substring(0, 4).toLowerCase() === "<svg" || input.substring(0, 5).toLowerCase() === "<?xml"
  401. || input.substring(0, 9).toLowerCase() === "<!doctype") {
  402. //render page directly from svg file
  403. svg = page.evaluate(loadChart, input, outType, messages);
  404. page.viewportSize = { width: svg.width, height: svg.height };
  405. renderSVG(svg);
  406. } else {
  407. // We have a js file, let highcharts create the chart first and grab the svg
  408. // load necessary libraries
  409. page.injectJs(config.JQUERY);
  410. page.injectJs(config.HIGHCHARTS);
  411. page.injectJs(config.HIGHCHARTS_MORE);
  412. page.injectJs(config.HIGHCHARTS_DATA);
  413. // load chart in page and return svg height and width
  414. svg = page.evaluate(createChart, width, constr, input, globalOptions, dataOptions, customCode, outType, callback, messages);
  415. if (!window.optionsParsed) {
  416. exit('ERROR: the options variable was not available, contains the infile an syntax error? see' + input);
  417. }
  418. if (callback !== undefined && !window.callbackParsed) {
  419. exit('ERROR: the callback variable was not available, contains the callbackfile an syntax error? see' + callback);
  420. }
  421. renderSVG(svg);
  422. }
  423. });
  424. }
  425. };
  426. startServer = function (host, port) {
  427. var server = require('webserver').create();
  428. server.listen(host + ':' + port,
  429. function (request, response) {
  430. var jsonStr = request.post,
  431. params,
  432. msg;
  433. try {
  434. params = JSON.parse(jsonStr);
  435. if (params.status) {
  436. // for server health validation
  437. response.statusCode = 200;
  438. response.write('OK');
  439. response.close();
  440. } else {
  441. render(params, function (result) {
  442. response.statusCode = 200;
  443. response.write(result);
  444. response.close();
  445. });
  446. }
  447. } catch (e) {
  448. msg = "Failed rendering: \n" + e;
  449. response.statusCode = 500;
  450. response.setHeader('Content-Type', 'text/plain');
  451. response.setHeader('Content-Length', msg.length);
  452. response.write(msg);
  453. response.close();
  454. }
  455. }); // end server.listen
  456. // switch to serverMode
  457. serverMode = true;
  458. console.log("OK, PhantomJS is ready.");
  459. };
  460. args = mapCLArguments();
  461. // set tmpDir, for output temporary files.
  462. if (args.tmpdir === undefined) {
  463. config.tmpDir = fs.workingDirectory + '/tmp';
  464. } else {
  465. config.tmpDir = args.tmpdir;
  466. }
  467. // exists tmpDir and is it writable?
  468. if (!fs.exists(config.tmpDir)) {
  469. try{
  470. fs.makeDirectory(config.tmpDir);
  471. } catch (e) {
  472. console.log('ERROR: Cannot make temp directory');
  473. }
  474. }
  475. if (args.host !== undefined && args.port !== undefined) {
  476. startServer(args.host, args.port);
  477. } else {
  478. // presume commandline usage
  479. render(args, function (msg) {
  480. console.log(msg);
  481. phantom.exit();
  482. });
  483. }
  484. }());