PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.7.1
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.7.1
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / js / render-chartjs.js

render-chartjs.js in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.7.1, at js/render-chartjs.js

466 lines 16.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global console */
2 /* global visualizer */
3 /* global Chart */
4 /* global numeral */
5 /* global moment */
6
7 (function($) {
8 var all_charts;
9 // so that we know which charts belong to our library.
10 var rendered_charts = [];
11
12 function renderChart(id, v) {
13 renderSpecificChart(id, all_charts[id], v);
14 }
15
16 function renderSpecificChart(id, chart, v) {
17 var render, container, series, data, datasets, settings, i, j, row, date, axis, property, format, formatter, type, rows, cols, labels;
18
19 if(chart.library !== 'chartjs'){
20 return;
21 }
22 rendered_charts[id] = 'yes';
23
24 series = chart.series;
25 data = chart.data;
26 settings = chart.settings;
27
28 container = document.getElementById(id);
29 if (container == null) {
30 return;
31 }
32
33 // eliminate the jitter/flicker while editing charts.
34 if(v.is_front == false){ // jshint ignore:line
35 $('#' + id).empty();
36 }
37
38 if($('#' + id + ' canvas').length === 0){
39 $('#' + id).append($('<canvas width="100%" height="90%"></canvas>'));
40 }
41
42 var context = $('#' + id + ' canvas')[0].getContext('2d');
43
44 type = chart.type;
45 switch (chart.type) {
46 case 'column':
47 type = 'bar';
48 break;
49 case 'bar':
50 type = 'horizontalBar';
51 break;
52 case 'pie':
53 // donut is not a setting but a separate chart type.
54 if(typeof settings['custom'] !== 'undefined' && settings['custom']['donut'] === 'true'){
55 type = 'doughnut';
56 }
57 break;
58 }
59
60 rows = [];
61 datasets = [];
62 labels = [];
63
64 for (i = 0; i < data.length; i++) {
65 row = [];
66 for (j = 0; j < series.length; j++) {
67 if (series[j].type === 'date' || series[j].type === 'datetime') {
68 date = new Date(data[i][j]);
69 data[i][j] = null;
70 if (Object.prototype.toString.call(date) === "[object Date]") {
71 if (!isNaN(date.getTime())) {
72 data[i][j] = date;
73 }
74 }
75 }
76 row.push(format_data(data[i][j], j, settings, series));
77 }
78 rows.push(row);
79 }
80
81 // transpose
82 for (j = 0; j < series.length; j++) {
83 row = [];
84 for (i = 0; i < rows.length; i++) {
85 if(j === 0){
86 labels.push(rows[i][j]);
87 }else{
88 row.push(rows[i][j]);
89 }
90 }
91 if(row.length > 0){
92 var $attributes = {label: series[j].label, data: row};
93 switch(chart.type){
94 case 'pie':
95 case 'polarArea':
96 $.extend($attributes, {label: labels});
97 handlePieSeriesSettings($attributes, rows, settings, chart);
98 break;
99 default:
100 handleSeriesSettings($attributes, j - 1, settings, chart);
101 }
102 datasets.push($attributes);
103 }
104 }
105
106 if(v.is_front == false){ // jshint ignore:line
107 // this line needs to be included twice. This is not an error.
108 // if this one is removed, the preview gets messed up.
109 // if this line is included all the time, in this very place (out of the if), the front-end gets messed up.
110 $.extend(settings, { responsive: true, maintainAspectRatio: false });
111 }
112
113 handleSettings(settings, chart);
114
115 var chartjs = new Chart(context, {
116 type: type,
117 data: {
118 labels: labels,
119 datasets: datasets
120 },
121 options: settings,
122 plugins: [{
123 afterRender: function () {
124 var canvas = $( 'canvas' )[0];
125 if ( $( '#chart-img' ).length ) {
126 $( '#chart-img' ).val( canvas.toDataURL() );
127 }
128 },
129 }],
130 });
131
132 // this line needs to be included twice. This is not an error.
133 $.extend(settings, { responsive: true, maintainAspectRatio: false });
134
135 // chart area
136 if(v.is_front == true){ // jshint ignore:line
137 $('#' + id).css('position', 'relative');
138 var height = settings.height.indexOf('%') === -1 ? ( settings.height + 'px' ) : settings.height;
139 var width = settings.width.indexOf('%') === -1 ? ( settings.width + 'px' ) : settings.width;
140 if(settings.height){
141 chartjs.canvas.parentNode.style.height = height;
142 $('#' + id + ' canvas').css('height', height);
143 }
144 if(settings.width){
145 chartjs.canvas.parentNode.style.width = width;
146 $('#' + id + ' canvas').css('width', width);
147 }
148 }
149
150 // allow user to extend the settings.
151 $('body').trigger('visualizer:chart:settings:extend', {id: id, chart: chart, settings: settings});
152
153 $('.loader').remove();
154 }
155
156 function handleSettings(settings, chart){
157 if(typeof settings === 'undefined'){
158 return;
159 }
160
161 // handle some defaults/idiosyncrasies.
162 if(typeof settings['animation'] !== 'undefined' && parseInt(settings['animation']['duration']) === 0){
163 settings['animation']['duration'] = 1000;
164 }
165
166 if(typeof settings['title'] !== 'undefined' && settings['title']['text'] !== ''){
167 settings['title']['display'] = true;
168 }
169
170 if(typeof settings['tooltip'] !== 'undefined' && typeof settings['tooltip']['intersect'] !== 'undefined'){
171 // jshint ignore:line
172 settings['tooltip']['intersect'] = settings['tooltip']['intersect'] == true || parseInt(settings['tooltip']['intersect']) === 1; // jshint ignore:line
173 }
174
175 if(typeof settings['fontName'] !== 'undefined' && settings['fontName'] !== ''){
176 Chart.defaults.font.family = settings['fontName'];
177 delete settings['fontName'];
178 }
179
180 if(typeof settings['fontSize'] !== 'undefined' && settings['fontSize'] !== ''){
181 Chart.defaults.font.size = parseInt(settings['fontSize']);
182 delete settings['fontSize'];
183 }
184
185 // handle legend defaults.
186 if(typeof settings['legend'] !== 'undefined' && typeof settings['legend']['labels'] !== 'undefined') {
187 for(var i in settings['legend']['labels']){
188 if(settings['legend']['labels'][i] !== 'undefined' && settings['legend']['labels'][i] === ''){
189 delete settings['legend']['labels'][i];
190 }
191 }
192 }
193
194 handleAxes(settings, chart);
195
196 override(settings, chart);
197 }
198
199 function handleAxes(settings, chart){
200 if(typeof settings['yAxes'] !== 'undefined' && typeof settings['xAxes'] !== 'undefined'){
201 // stacking has to be defined on both axes.
202 if(typeof settings['yAxes']['stacked_bool'] !== 'undefined'){
203 settings['xAxes']['stacked_bool'] = 'true';
204 }
205 if(typeof settings['xAxes']['stacked_bool'] !== 'undefined'){
206 settings['yAxes']['stacked_bool'] = 'true';
207 }
208 }
209 configureAxes(settings, 'yAxes', chart);
210 configureAxes(settings, 'xAxes', chart);
211 }
212
213 function configureAxes(settings, axis, chart) {
214 if(typeof settings[axis] !== 'undefined'){
215 var $features = {};
216 for(var i in settings[axis]){
217 var $o = {};
218 if(Array.isArray(settings[axis][i]) || typeof settings[axis][i] === 'object'){
219 for(var j in settings[axis][i]){
220 var $val = '';
221 if(j === 'labelString'){
222 $o['display'] = true;
223 $val = settings[axis][i][j];
224 }else if(i === 'ticks'){
225 // number values under ticks need to be converted to numbers or the library throws a JS error.
226 $val = parseFloat(settings[axis][i][j]);
227 if(isNaN($val)){
228 $val = '';
229 }
230 } else {
231 $val = settings[axis][i][j];
232 }
233 if($val !== ''){
234 $o[j] = $val;
235 }
236 }
237 }else{
238 // usually for attributes that have primitive values.
239 var array = i.split('_');
240 var dataType = 'string';
241 var dataValue = settings[axis][i];
242 if(array.length === 2){
243 dataType = array[1];
244 }
245
246 if(settings[axis][i] === ''){
247 continue;
248 }
249 switch(dataType){
250 case 'bool':
251 dataValue = dataValue === 'true' ? true : false;
252 break;
253 case 'int':
254 dataValue = parseFloat(dataValue);
255 break;
256 }
257 $o = dataValue;
258 // remove the type suffix to get the name of the setting.
259 i = i.replace(/_bool/g, '').replace(/_int/g, '');
260 }
261 $features[i] = $o;
262 }
263 var $scales = {};
264 $scales['scales'] = {};
265 $scales['scales'][axis] = [];
266 if(typeof settings['scales'] !== 'undefined' && typeof settings[axis + 'set'] === 'undefined'){
267 $scales['scales'] = settings['scales'];
268 if(typeof settings['scales'][axis] !== 'undefined'){
269 $scales['scales'][axis] = settings['scales'][axis];
270 }
271 }
272 if(typeof $scales['scales'][axis] === 'undefined'){
273 $scales['scales'][axis] = [];
274 }
275 var $axis = $scales['scales'][axis];
276
277 $axis.push($features);
278 $.extend(settings, $scales);
279
280 // to prevent duplication, indicates that the axis has been set.
281 var $custom = {};
282 $custom[axis + 'set'] = 'yes';
283 $.extend(settings, $custom);
284 }
285
286 // format the axes labels.
287 if(typeof settings[axis + '_format'] !== 'undefined' && settings[axis + '_format'] !== ''){
288 var format = settings[axis + '_format'];
289 switch(axis){
290 case 'xAxes':
291 settings.scales.xAxes[0].ticks.callback = function(value, index, values){
292 return format_datum(value, format);
293 };
294 break;
295 case 'yAxes':
296 settings.scales.yAxes[0].ticks.callback = function(value, index, values){
297 return format_datum(value, format);
298 };
299 break;
300 }
301 delete settings[axis + '_format'];
302 }
303 delete settings[axis];
304 }
305
306 function handlePieSeriesSettings($attributes, rows, settings, chart){
307 if(typeof settings.slices === 'undefined'){
308 return;
309 }
310
311 var atts = [];
312 // collect all the types of attributes
313 for(var j in settings.slices[0]){
314 // weight screws up the rendering for some reason, so we will ignore it.
315 if(j === 'weight') {
316 continue;
317 }
318 atts.push(j);
319 }
320
321 for (j = 0; j < atts.length; j++) {
322 var values = [];
323 for (var i = 0; i < rows.length; i++) {
324 if(typeof settings.slices[i] !== 'undefined' && typeof settings.slices[i][atts[j]] !== 'undefined'){
325 values.push(settings.slices[i][atts[j]]);
326 }
327 }
328 var object = {};
329 object[ atts[ j ] ] = values;
330 $.extend($attributes, object);
331 }
332 }
333
334 function handleSeriesSettings($attributes, j, settings, chart){
335 if(typeof settings.series === 'undefined' || typeof settings.series[j] === 'undefined'){
336 return;
337 }
338 for(var i in settings.series[j]){
339 var $attribute = {};
340 if ( settings.series[j].backgroundColor == '' ) {
341 delete settings.series[j].backgroundColor;
342 }
343 $attribute[i] = settings.series[j][i];
344 $.extend($attributes, $attribute);
345 }
346 }
347
348 function format_datum(datum, format, type){
349 if(format === '' || format === null || typeof format === 'undefined'){
350 return datum;
351 }
352 // if there is no type, this is probably coming from the axes formatting.
353 var removeDollar = true;
354 if(typeof type === 'undefined' || type === null){
355 // we will determine type on the basis of the presence or absence of #.
356 type = 'date';
357 if(format.indexOf('#') !== -1){
358 type = 'number';
359 }
360 removeDollar = false;
361 }
362
363 switch(type) {
364 case 'number':
365 // numeral.js works on 0 instead of # so we just replace that in the ICU pattern set.
366 format = format.replace(/#/g, '0');
367 // we also replace all instance of '$' as that is more relevant for ticks.
368 if(removeDollar){
369 format = format.replace(/\$/g, '');
370 }
371 datum = numeral(datum).format(format);
372 break;
373 case 'date':
374 case 'datetime':
375 case 'timeofday':
376 datum = moment(datum).format(format);
377 break;
378 }
379 return datum;
380 }
381
382 function format_data(datum, j, settings, series){
383 j = j - 1;
384 var format = typeof settings.series !== 'undefined' && typeof settings.series[j] !== 'undefined' ? settings.series[j].format : '';
385 return format_datum(datum, format, series[j + 1].type);
386 }
387
388 function override(settings, chart) {
389 if (settings.manual) {
390 try{
391 var options = JSON.parse(settings.manual);
392 $.extend(settings, options);
393 delete settings.manual;
394 }catch(error){
395 console.error("Error while adding manual configuration override " + settings.manual);
396 }
397 }
398 }
399
400
401 function render(v) {
402 for (var id in (all_charts || {})) {
403 renderChart(id, v);
404 }
405 }
406
407 $('body').on('visualizer:render:chart:start', function(event, v){
408 all_charts = v.charts;
409
410 if(v.is_front == true && typeof v.id !== 'undefined'){ // jshint ignore:line
411 renderChart(v.id, v);
412 } else {
413 render(v);
414 }
415
416 // for some reason this needs to be introduced here for dynamic preview updates to work.
417 v.update = function(){
418 renderChart('canvas', v);
419 };
420
421 });
422
423 $('body').on('visualizer:render:specificchart:start', function(event, v){
424 renderSpecificChart(v.id, v.chart, v.v);
425 });
426
427 $('body').on('visualizer:render:currentchart:update', function(event, v){
428 var data = v || event.detail;
429 renderChart('canvas', data.visualizer);
430 });
431
432 // front end actions
433 // 'image' is also called from the library
434 $('body').on('visualizer:action:specificchart', function(event, v){
435 var id = v.id;
436 if(typeof rendered_charts[id] === 'undefined'){
437 return;
438 }
439 var canvas = $('#' + id + ' canvas');
440 switch(v.action){
441 case 'print':
442 var win = window.open();
443 win.document.write("<br><img src='" + canvas[0].toDataURL() + "'/>");
444 win.document.close();
445 win.onload = function () { win.print(); setTimeout(win.close, 500); };
446 break;
447 case 'image':
448 var img = canvas[0].toDataURL();
449 if(img !== ''){
450 var $a = $("<a>"); // jshint ignore:line
451 $a.attr("href", img);
452 $("body").append($a);
453 $a.attr("download", v.dataObj.name);
454 $a[0].click();
455 $a.remove();
456 }else{
457 console.warn("No image generated");
458 }
459 break;
460 }
461 });
462
463 })(jQuery);
464
465
466