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

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