phantom_graph 0.0.4

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