canvas_exporting 0.0.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +26 -0
- data/lib/canvas_exporting/exporter.rb +102 -0
- data/lib/canvas_exporting/version.rb +3 -0
- data/lib/canvas_exporting.rb +7 -0
- data/phantomjs/callback.js +1 -0
- data/phantomjs/canvas-convert.js +290 -0
- data/phantomjs/canvasjs.js +13155 -0
- data/phantomjs/jquery.1.9.1.min.js +5 -0
- data/phantomjs/treemap.js +29 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 87c0ec1e5a813f6c937e22ed3bb51cf1f25297c3
|
4
|
+
data.tar.gz: c53831121a9dd9ae28e2aa156ad83ebdf81b7311
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7b23a241c14c8754f7d9fcf461fb7e2428396a42e5d6972f125759541993fb8030eb2578b3f9a78ec881c3833518a658c4d431b4896b741ef6323595fa2cab89
|
7
|
+
data.tar.gz: dcd0e8a762d7bfe781a061b564c400bc3b7e305566c64e3b922613ca3134743c2fa8a1de259c97cff074339fe948500dedff304ade93b888e7b84911c5d71921
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 bastengao
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'HighchartsExporting'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
require File.join('rspec', 'core', 'rake_task')
|
24
|
+
RSpec::Core::RakeTask.new(:spec)
|
25
|
+
|
26
|
+
task :default => :spec
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'phantomjs'
|
4
|
+
|
5
|
+
# references:
|
6
|
+
# http://www.highcharts.com/docs/export-module/export-module-overview
|
7
|
+
# https://github.com/highslide-software/highcharts.com/tree/master/exporting-server/phantomjs
|
8
|
+
module CanvasExporting
|
9
|
+
module Exporter
|
10
|
+
def export
|
11
|
+
@infile_tmp_file = infile_file
|
12
|
+
|
13
|
+
type = params[:type] || 'image/png'
|
14
|
+
extension = MIME::Types[type].first.extensions.first
|
15
|
+
|
16
|
+
if params[:outputpath] == nil
|
17
|
+
output_path = tmp_dir
|
18
|
+
else
|
19
|
+
output_path = params[:outputpath]
|
20
|
+
end
|
21
|
+
|
22
|
+
output_path = output_path + '/' if output_path[-1] != '/'
|
23
|
+
|
24
|
+
if params[:filename] == nil
|
25
|
+
filename = 'Chart.' + extension
|
26
|
+
else
|
27
|
+
filename = params[:filename]
|
28
|
+
end
|
29
|
+
|
30
|
+
@output_file = output_path + filename
|
31
|
+
|
32
|
+
scale = params[:scale] || 2
|
33
|
+
width = params[:width]
|
34
|
+
constr = params[:constr] || 'Chart'
|
35
|
+
|
36
|
+
convert_args = convert_args({infile: @infile_tmp_file.path,
|
37
|
+
outfile: @output_file,
|
38
|
+
scale: scale,
|
39
|
+
width: width,
|
40
|
+
constr: constr,
|
41
|
+
callback: callback_path
|
42
|
+
})
|
43
|
+
|
44
|
+
result = ::Phantomjs.run(*convert_args)
|
45
|
+
#puts result if VERBOSE
|
46
|
+
|
47
|
+
# TODO: clean @output_tmp_file
|
48
|
+
@infile_tmp_file.delete
|
49
|
+
@callback_tmp_file.delete if @callback_tmp_file
|
50
|
+
|
51
|
+
if /Error/ =~ result
|
52
|
+
render text: result, status: 500
|
53
|
+
else
|
54
|
+
send_file @output_file, filename: "#{filename}", type: type
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
def tmp_dir
|
60
|
+
Rails.root.join('tmp/canvas_charts').to_s.tap { |f| FileUtils.mkdir_p f }
|
61
|
+
end
|
62
|
+
|
63
|
+
def infile_file
|
64
|
+
tmp_file = nil
|
65
|
+
if params[:options]
|
66
|
+
tmp_file = Tempfile.new(['options', '.json'], tmp_dir)
|
67
|
+
temp_write(tmp_file, params[:options])
|
68
|
+
elsif params[:svg]
|
69
|
+
tmp_file = Tempfile.new(['options', '.svg'], tmp_dir)
|
70
|
+
temp_write(tmp_file, params[:svg])
|
71
|
+
end
|
72
|
+
|
73
|
+
tmp_file
|
74
|
+
end
|
75
|
+
|
76
|
+
def callback_path
|
77
|
+
if params[:callback]
|
78
|
+
@callback_tmp_file = Tempfile.new(['callbacks', '.js'], tmp_dir)
|
79
|
+
temp_write(@callback_tmp_file, params[:callback])
|
80
|
+
@callback_tmp_file.path
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def convert_args(args)
|
87
|
+
convert_args = args.reject { |k, v| v.blank? }.to_a.map { |pair| ["-#{pair[0]}", pair[1].to_s] }.flatten
|
88
|
+
|
89
|
+
convert_args.unshift("--web-security=false", convert_js_path)
|
90
|
+
end
|
91
|
+
|
92
|
+
def convert_js_path
|
93
|
+
File.join(ROOT, 'phantomjs/canvas-convert.js').to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def temp_write(tmp_file, content)
|
97
|
+
File.open(tmp_file.path, 'r+') do |f|
|
98
|
+
f.write content
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
var dummy = 0;
|
@@ -0,0 +1,290 @@
|
|
1
|
+
|
2
|
+
(function () {
|
3
|
+
"use strict";
|
4
|
+
|
5
|
+
var config = {
|
6
|
+
/* define locations of mandatory javascript files.
|
7
|
+
*/
|
8
|
+
files: {
|
9
|
+
JQUERY: 'jquery.1.9.1.min.js',
|
10
|
+
/* JQUERY: 'jquery.js', */
|
11
|
+
CANVAS: 'canvasjs.js',
|
12
|
+
CANVAS_EXPORT: 'excanvas.js'
|
13
|
+
},
|
14
|
+
TIMEOUT: 5000 /* 5 seconds timout for loading images */
|
15
|
+
},
|
16
|
+
mapCLArguments,
|
17
|
+
render,
|
18
|
+
startServer = false,
|
19
|
+
args,
|
20
|
+
pick,
|
21
|
+
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\">',
|
22
|
+
dpiCorrection = 1.4,
|
23
|
+
system = require('system'),
|
24
|
+
fs = require('fs'),
|
25
|
+
serverMode = false;
|
26
|
+
|
27
|
+
pick = function () {
|
28
|
+
var args = arguments, i, arg, length = args.length;
|
29
|
+
for (i = 0; i < length; i += 1) {
|
30
|
+
arg = args[i];
|
31
|
+
if (arg !== undefined && arg !== null && arg !== 'null' && arg != '0') {
|
32
|
+
return arg;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
};
|
36
|
+
|
37
|
+
mapCLArguments = function () {
|
38
|
+
var map = {},
|
39
|
+
i,
|
40
|
+
key;
|
41
|
+
|
42
|
+
if (system.args.length < 1) {
|
43
|
+
console.log('Commandline Usage: canvas-convert.js -infile URL -outfile filename -scale 2.5 -width 300 -constr Chart -callback callback.js');
|
44
|
+
console.log(', or run PhantomJS as server: canvas-convert.js -host 127.0.0.1 -port 1234');
|
45
|
+
}
|
46
|
+
|
47
|
+
for (i = 0; i < system.args.length; i += 1) {
|
48
|
+
if (system.args[i].charAt(0) === '-') {
|
49
|
+
key = system.args[i].substr(1, i.length);
|
50
|
+
if (key === 'infile' || key === 'callback' || key === 'dataoptions' || key === 'globaloptions' || key === 'customcode') {
|
51
|
+
// get string from file
|
52
|
+
try {
|
53
|
+
map[key] = fs.read(system.args[i + 1]).replace(/^\s+/, '');
|
54
|
+
} catch (e) {
|
55
|
+
console.log('Error: cannot find file, ' + system.args[i + 1]);
|
56
|
+
phantom.exit();
|
57
|
+
}
|
58
|
+
} else {
|
59
|
+
map[key] = system.args[i + 1];
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
return map;
|
64
|
+
};
|
65
|
+
|
66
|
+
render = function (params, exitCallback) {
|
67
|
+
|
68
|
+
var page = require('webpage').create(),
|
69
|
+
messages = {},
|
70
|
+
scaleAndClipPage,
|
71
|
+
loadChart,
|
72
|
+
createChart,
|
73
|
+
input,
|
74
|
+
constr,
|
75
|
+
callback,
|
76
|
+
width,
|
77
|
+
output,
|
78
|
+
outType,
|
79
|
+
timer,
|
80
|
+
renderSVG,
|
81
|
+
convert,
|
82
|
+
exit,
|
83
|
+
interval,
|
84
|
+
counter,
|
85
|
+
imagesLoaded = false;
|
86
|
+
|
87
|
+
messages.optionsParsed = 'Canvas.options.parsed';
|
88
|
+
messages.callbackParsed = 'Canvas.cb.parsed';
|
89
|
+
|
90
|
+
window.optionsParsed = false;
|
91
|
+
window.callbackParsed = false;
|
92
|
+
|
93
|
+
page.onConsoleMessage = function (msg) {
|
94
|
+
console.log(msg);
|
95
|
+
|
96
|
+
/*
|
97
|
+
* Ugly hack, but only way to get messages out of the 'page.evaluate()'
|
98
|
+
* sandbox. If any, please contribute with improvements on this!
|
99
|
+
*/
|
100
|
+
|
101
|
+
/* to check options or callback are properly parsed */
|
102
|
+
if (msg === messages.optionsParsed) {
|
103
|
+
window.optionsParsed = true;
|
104
|
+
}
|
105
|
+
|
106
|
+
if (msg === messages.callbackParsed) {
|
107
|
+
window.callbackParsed = true;
|
108
|
+
}
|
109
|
+
};
|
110
|
+
|
111
|
+
page.onAlert = function (msg) {
|
112
|
+
console.log(msg);
|
113
|
+
};
|
114
|
+
|
115
|
+
|
116
|
+
exit = function (result) {
|
117
|
+
if (serverMode) {
|
118
|
+
//Calling page.close(), may stop the increasing heap allocation
|
119
|
+
page.close();
|
120
|
+
}
|
121
|
+
exitCallback(result);
|
122
|
+
};
|
123
|
+
|
124
|
+
loadChart = function( string_in, outputType ) {
|
125
|
+
|
126
|
+
try {
|
127
|
+
|
128
|
+
var container, htmlout;
|
129
|
+
|
130
|
+
$(document.body).css('margin', '0px');
|
131
|
+
|
132
|
+
$(document.body).css('backgroundColor', 'white');
|
133
|
+
|
134
|
+
container = $('<div>').appendTo(document.body);
|
135
|
+
container.attr('id', 'chartContainer' );
|
136
|
+
// var sizing = 'width: ' + width + 'px;';
|
137
|
+
// $container.attr('style', sizing );
|
138
|
+
//console.log(" json_in: " + JSON.parse(json_in) );
|
139
|
+
var json_in = JSON.parse(string_in);
|
140
|
+
//json_in = JSON.parse(json_in);
|
141
|
+
json_in["animationEnabled"] = false;
|
142
|
+
|
143
|
+
//console.log ("just before canvas rendering");
|
144
|
+
|
145
|
+
var chartTest = new CanvasJS.Chart("chartContainer", json_in );
|
146
|
+
console.log ("complete the object create");
|
147
|
+
|
148
|
+
var rs = chartTest.render();
|
149
|
+
console.log ("completed the rendering");
|
150
|
+
|
151
|
+
var htmlout = $('.canvasjs-chart-canvas')[0];
|
152
|
+
//var imageData = htmlout.toDataURL(outType, 0.92 );
|
153
|
+
var imageData = htmlout.toDataURL('image/'+outputType, 0.92 );
|
154
|
+
} catch (e) {
|
155
|
+
console.log('ERROR: Cannot create CanvasJS object.');
|
156
|
+
console.log('Error message: ' + e.number + " - " + e.message)
|
157
|
+
return { html: "<p>Error output</p>" };
|
158
|
+
}
|
159
|
+
return {
|
160
|
+
html: imageData
|
161
|
+
};
|
162
|
+
};
|
163
|
+
|
164
|
+
|
165
|
+
if (params.length < 1) {
|
166
|
+
exit("Error: Insufficient parameters");
|
167
|
+
} else {
|
168
|
+
input = params.infile;
|
169
|
+
output = params.outfile;
|
170
|
+
console.log("input: " + input);
|
171
|
+
console.log("output: " + output);
|
172
|
+
|
173
|
+
if (output !== undefined) {
|
174
|
+
outType = pick(output.split('.').pop(),'png');
|
175
|
+
} else {
|
176
|
+
outType = pick(params.type,'png');
|
177
|
+
}
|
178
|
+
|
179
|
+
constr = pick(params.constr, 'Chart');
|
180
|
+
callback = params.callback;
|
181
|
+
width = params.width;
|
182
|
+
|
183
|
+
if (input === undefined || input.length === 0) {
|
184
|
+
exit('Error: Insuficient or wrong parameters for rendering');
|
185
|
+
}
|
186
|
+
|
187
|
+
page.open('about:blank', function (status) {
|
188
|
+
var svg,
|
189
|
+
globalOptions = params.globaloptions,
|
190
|
+
dataOptions = params.dataoptions,
|
191
|
+
customCode = 'function customCode(options) {\n' + params.customcode + '}\n',
|
192
|
+
jsfile;
|
193
|
+
|
194
|
+
// load necessary libraries
|
195
|
+
for (jsfile in config.files) {
|
196
|
+
if (config.files.hasOwnProperty(jsfile)) {
|
197
|
+
page.injectJs(config.files[jsfile]);
|
198
|
+
//console.log("injecting: " + config.files[jsfile] );
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
var rs = page.evaluate(loadChart, input, outType );
|
203
|
+
|
204
|
+
try {
|
205
|
+
var result = rs["html"].split(",")
|
206
|
+
var result_blob = window.atob(result[1])
|
207
|
+
var imageFile = fs.open(params.outfile, "wb");
|
208
|
+
imageFile.write(result_blob);
|
209
|
+
imageFile.close();
|
210
|
+
} catch (e) {
|
211
|
+
console.log("error evaluating document object. error: " + e.message);
|
212
|
+
}
|
213
|
+
|
214
|
+
// return the tmp file name and path to the calling process
|
215
|
+
exit(params.outfile)
|
216
|
+
|
217
|
+
});
|
218
|
+
}
|
219
|
+
};
|
220
|
+
|
221
|
+
startServer = function (host, port) {
|
222
|
+
var server = require('webserver').create();
|
223
|
+
|
224
|
+
server.listen(host + ':' + port,
|
225
|
+
function (request, response) {
|
226
|
+
var jsonStr = request.postRaw || request.post,
|
227
|
+
params,
|
228
|
+
msg;
|
229
|
+
try {
|
230
|
+
params = JSON.parse(jsonStr);
|
231
|
+
if (params.status) {
|
232
|
+
// for server health validation
|
233
|
+
response.statusCode = 200;
|
234
|
+
response.write('OK');
|
235
|
+
response.close();
|
236
|
+
} else {
|
237
|
+
render(params, function (result) {
|
238
|
+
response.statusCode = 200;
|
239
|
+
response.write(result);
|
240
|
+
response.close();
|
241
|
+
});
|
242
|
+
}
|
243
|
+
} catch (e) {
|
244
|
+
msg = "Failed rendering: \n" + e;
|
245
|
+
response.statusCode = 500;
|
246
|
+
response.setHeader('Content-Type', 'text/plain');
|
247
|
+
response.setHeader('Content-Length', msg.length);
|
248
|
+
response.write(msg);
|
249
|
+
response.close();
|
250
|
+
}
|
251
|
+
}); // end server.listen
|
252
|
+
|
253
|
+
// switch to serverMode
|
254
|
+
serverMode = true;
|
255
|
+
|
256
|
+
console.log("OK, PhantomJS is ready.");
|
257
|
+
};
|
258
|
+
|
259
|
+
args = mapCLArguments();
|
260
|
+
|
261
|
+
//console.log("made it past the args mapper")
|
262
|
+
//console.log("arguments: " + JSON.stringify(args));
|
263
|
+
// set tmpDir, for output temporary files.
|
264
|
+
if (args.tmpdir === undefined) {
|
265
|
+
config.tmpDir = fs.workingDirectory + '/tmp';
|
266
|
+
} else {
|
267
|
+
config.tmpDir = args.tmpdir;
|
268
|
+
}
|
269
|
+
|
270
|
+
// exists tmpDir and is it writable?
|
271
|
+
if (!fs.exists(config.tmpDir)) {
|
272
|
+
try{
|
273
|
+
fs.makeDirectory(config.tmpDir);
|
274
|
+
} catch (e) {
|
275
|
+
console.log('ERROR: Cannot create temp directory for ' + config.tmpDir);
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
|
280
|
+
if (args.host !== undefined && args.port !== undefined) {
|
281
|
+
startServer(args.host, args.port);
|
282
|
+
} else {
|
283
|
+
// presume commandline usage
|
284
|
+
//console.log("calling the renderer");
|
285
|
+
render(args, function (msg) {
|
286
|
+
console.log(msg);
|
287
|
+
phantom.exit();
|
288
|
+
});
|
289
|
+
}
|
290
|
+
}());
|