PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.6
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.6
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 4.0.6, at js/render-chartjs.js

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