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.
- checksums.yaml +15 -0
- data/.gitignore +7 -0
- data/.rspec +3 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +78 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +72 -0
- data/Rakefile +38 -0
- data/lib/phantom_graph.rb +7 -0
- data/lib/phantom_graph/base.rb +7 -0
- data/lib/phantom_graph/convert.rb +6 -0
- data/lib/phantom_graph/convert/highchart.rb +107 -0
- data/lib/phantom_graph/convert/stockchart.rb +11 -0
- data/lib/phantom_graph/error.rb +19 -0
- data/lib/phantom_graph/js/data.js +16 -0
- data/lib/phantom_graph/js/gray.js +257 -0
- data/lib/phantom_graph/js/highcharts-convert.js +506 -0
- data/lib/phantom_graph/js/highcharts-more.js +51 -0
- data/lib/phantom_graph/js/highcharts.js +908 -0
- data/lib/phantom_graph/js/highstock.js +1132 -0
- data/lib/phantom_graph/js/jquery.js +5 -0
- data/lib/phantom_graph/js/readme.md +48 -0
- data/lib/phantom_graph/setting.rb +67 -0
- data/lib/phantom_graph/version.rb +3 -0
- data/lib/tasks/phantom_graph_tasks.rake +4 -0
- data/phantom_graph.gemspec +29 -0
- data/spec/phantom_graph/convert/14892-1372331188.png +0 -0
- data/spec/phantom_graph/convert/56666-1372331186.png +0 -0
- data/spec/phantom_graph/convert/highchart_spec.rb +101 -0
- data/spec/phantom_graph/convert/stock.json +256 -0
- data/spec/phantom_graph/convert/stockchart_spec.rb +71 -0
- data/spec/phantom_graph/setting_spec.rb +85 -0
- data/spec/spec_helper.rb +6 -0
- metadata +143 -0
@@ -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
|
+
}());
|