PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.8.1
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.8.1
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 3.10.4 All 148 releases
visualizer / js / render-chartjs.js

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

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