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 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,3 @@
1
+ module CanvasExporting
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'canvas_exporting/version'
2
+ require 'canvas_exporting/exporter'
3
+
4
+ module CanvasExporting
5
+ ROOT = File.expand_path("../..", __FILE__)
6
+ VERBOSE = false
7
+ 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
+ }());