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

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