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

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