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

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