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

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