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

463 lines 16.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 = 'horizontalBar';
51 break;
52 case 'pie':
53 // donut is not a setting but a separate chart type.
54 if(typeof settings['custom'] !== 'undefined' && settings['custom']['donut'] === 'true'){
55 type = 'doughnut';
56 }
57 break;
58 }
59
60 rows = [];
61 datasets = [];
62 labels = [];
63
64 for (i = 0; i < data.length; i++) {
65 row = [];
66 for (j = 0; j < series.length; j++) {
67 if (series[j].type === 'date' || series[j].type === 'datetime') {
68 date = new Date(data[i][j]);
69 data[i][j] = null;
70 if (Object.prototype.toString.call(date) === "[object Date]") {
71 if (!isNaN(date.getTime())) {
72 data[i][j] = date;
73 }
74 }
75 }
76 row.push(format_data(data[i][j], j, settings, series));
77 }
78 rows.push(row);
79 }
80
81 // transpose
82 for (j = 0; j < series.length; j++) {
83 row = [];
84 for (i = 0; i < rows.length; i++) {
85 if(j === 0){
86 labels.push(rows[i][j]);
87 }else{
88 row.push(rows[i][j]);
89 }
90 }
91 if(row.length > 0){
92 var $attributes = {label: series[j].label, data: row};
93 switch(chart.type){
94 case 'pie':
95 case 'polarArea':
96 $.extend($attributes, {label: labels});
97 handlePieSeriesSettings($attributes, rows, settings, chart);
98 break;
99 default:
100 handleSeriesSettings($attributes, j - 1, settings, chart);
101 }
102 datasets.push($attributes);
103 }
104 }
105
106 if(v.is_front == false){ // jshint ignore:line
107 // this line needs to be included twice. This is not an error.
108 // if this one is removed, the preview gets messed up.
109 // if this line is included all the time, in this very place (out of the if), the front-end gets messed up.
110 $.extend(settings, { responsive: true, maintainAspectRatio: false });
111 }
112
113 handleSettings(settings, chart);
114
115 var chartjs = new Chart(context, {
116 type: type,
117 data: {
118 labels: labels,
119 datasets: datasets
120 },
121 options: settings,
122 plugins: [{
123 afterRender: function () {
124 var canvas = $( 'canvas' )[0];
125 if ( $( '#chart-img' ).length ) {
126 $( '#chart-img' ).val( canvas.toDataURL() );
127 }
128 },
129 }],
130 });
131
132 // this line needs to be included twice. This is not an error.
133 $.extend(settings, { responsive: true, maintainAspectRatio: false });
134
135 // chart area
136 if(v.is_front == true){ // jshint ignore:line
137 $('#' + id).css('position', 'relative');
138 var height = settings.height.indexOf('%') === -1 ? ( settings.height + 'px' ) : settings.height;
139 var width = settings.width.indexOf('%') === -1 ? ( settings.width + 'px' ) : settings.width;
140 if(settings.height){
141 chartjs.canvas.parentNode.style.height = height;
142 $('#' + id + ' canvas').css('height', height);
143 }
144 if(settings.width){
145 chartjs.canvas.parentNode.style.width = width;
146 $('#' + id + ' canvas').css('width', width);
147 }
148 }
149
150 // allow user to extend the settings.
151 $('body').trigger('visualizer:chart:settings:extend', {id: id, chart: chart, settings: settings});
152
153 $('.loader').remove();
154 }
155
156 function handleSettings(settings, chart){
157 if(typeof settings === 'undefined'){
158 return;
159 }
160
161 // handle some defaults/idiosyncrasies.
162 if(typeof settings['animation'] !== 'undefined' && parseInt(settings['animation']['duration']) === 0){
163 settings['animation']['duration'] = 1000;
164 }
165
166 if(typeof settings['title'] !== 'undefined' && settings['title']['text'] !== ''){
167 settings['title']['display'] = true;
168 }
169
170 if(typeof settings['tooltip'] !== 'undefined' && typeof settings['tooltip']['intersect'] !== 'undefined'){
171 // jshint ignore:line
172 settings['tooltip']['intersect'] = settings['tooltip']['intersect'] == true || parseInt(settings['tooltip']['intersect']) === 1; // jshint ignore:line
173 }
174
175 if(typeof settings['fontName'] !== 'undefined' && settings['fontName'] !== ''){
176 Chart.defaults.global.defaultFontFamily = settings['fontName'];
177 delete settings['fontName'];
178 }
179
180 if(typeof settings['fontSize'] !== 'undefined' && settings['fontSize'] !== ''){
181 Chart.defaults.global.defaultFontSize = parseInt(settings['fontSize']);
182 delete settings['fontSize'];
183 }
184
185 // handle legend defaults.
186 if(typeof settings['legend'] !== 'undefined' && typeof settings['legend']['labels'] !== 'undefined') {
187 for(var i in settings['legend']['labels']){
188 if(settings['legend']['labels'][i] !== 'undefined' && settings['legend']['labels'][i] === ''){
189 delete settings['legend']['labels'][i];
190 }
191 }
192 }
193
194 handleAxes(settings, chart);
195
196 override(settings, chart);
197 }
198
199 function handleAxes(settings, chart){
200 if(typeof settings['yAxes'] !== 'undefined' && typeof settings['xAxes'] !== 'undefined'){
201 // stacking has to be defined on both axes.
202 if(typeof settings['yAxes']['stacked_bool'] !== 'undefined'){
203 settings['xAxes']['stacked_bool'] = 'true';
204 }
205 if(typeof settings['xAxes']['stacked_bool'] !== 'undefined'){
206 settings['yAxes']['stacked_bool'] = 'true';
207 }
208 }
209 configureAxes(settings, 'yAxes', chart);
210 configureAxes(settings, 'xAxes', chart);
211 }
212
213 function configureAxes(settings, axis, chart) {
214 if(typeof settings[axis] !== 'undefined'){
215 var $features = {};
216 for(var i in settings[axis]){
217 var $o = {};
218 if(Array.isArray(settings[axis][i]) || typeof settings[axis][i] === 'object'){
219 for(var j in settings[axis][i]){
220 var $val = '';
221 if(j === 'labelString'){
222 $o['display'] = true;
223 $val = settings[axis][i][j];
224 }else if(i === 'ticks'){
225 // number values under ticks need to be converted to numbers or the library throws a JS error.
226 $val = parseFloat(settings[axis][i][j]);
227 if(isNaN($val)){
228 $val = '';
229 }
230 } else {
231 $val = settings[axis][i][j];
232 }
233 if($val !== ''){
234 $o[j] = $val;
235 }
236 }
237 }else{
238 // usually for attributes that have primitive values.
239 var array = i.split('_');
240 var dataType = 'string';
241 var dataValue = settings[axis][i];
242 if(array.length === 2){
243 dataType = array[1];
244 }
245
246 if(settings[axis][i] === ''){
247 continue;
248 }
249 switch(dataType){
250 case 'bool':
251 dataValue = dataValue === 'true' ? true : false;
252 break;
253 case 'int':
254 dataValue = parseFloat(dataValue);
255 break;
256 }
257 $o = dataValue;
258 // remove the type suffix to get the name of the setting.
259 i = i.replace(/_bool/g, '').replace(/_int/g, '');
260 }
261 $features[i] = $o;
262 }
263 var $scales = {};
264 $scales['scales'] = {};
265 $scales['scales'][axis] = [];
266 if(typeof settings['scales'] !== 'undefined' && typeof settings[axis + 'set'] === 'undefined'){
267 $scales['scales'] = settings['scales'];
268 if(typeof settings['scales'][axis] !== 'undefined'){
269 $scales['scales'][axis] = settings['scales'][axis];
270 }
271 }
272 if(typeof $scales['scales'][axis] === 'undefined'){
273 $scales['scales'][axis] = [];
274 }
275 var $axis = $scales['scales'][axis];
276
277 $axis.push($features);
278 $.extend(settings, $scales);
279
280 // to prevent duplication, indicates that the axis has been set.
281 var $custom = {};
282 $custom[axis + 'set'] = 'yes';
283 $.extend(settings, $custom);
284 }
285
286 // format the axes labels.
287 if(typeof settings[axis + '_format'] !== 'undefined' && settings[axis + '_format'] !== ''){
288 var format = settings[axis + '_format'];
289 switch(axis){
290 case 'xAxes':
291 settings.scales.xAxes[0].ticks.callback = function(value, index, values){
292 return format_datum(value, format);
293 };
294 break;
295 case 'yAxes':
296 settings.scales.yAxes[0].ticks.callback = function(value, index, values){
297 return format_datum(value, format);
298 };
299 break;
300 }
301 delete settings[axis + '_format'];
302 }
303 delete settings[axis];
304 }
305
306 function handlePieSeriesSettings($attributes, rows, settings, chart){
307 if(typeof settings.slices === 'undefined'){
308 return;
309 }
310
311 var atts = [];
312 // collect all the types of attributes
313 for(var j in settings.slices[0]){
314 // weight screws up the rendering for some reason, so we will ignore it.
315 if(j === 'weight') {
316 continue;
317 }
318 atts.push(j);
319 }
320
321 for (j = 0; j < atts.length; j++) {
322 var values = [];
323 for (var i = 0; i < rows.length; i++) {
324 if(typeof settings.slices[i] !== 'undefined' && typeof settings.slices[i][atts[j]] !== 'undefined'){
325 values.push(settings.slices[i][atts[j]]);
326 }
327 }
328 var object = {};
329 object[ atts[ j ] ] = values;
330 $.extend($attributes, object);
331 }
332 }
333
334 function handleSeriesSettings($attributes, j, settings, chart){
335 if(typeof settings.series === 'undefined' || typeof settings.series[j] === 'undefined'){
336 return;
337 }
338 for(var i in settings.series[j]){
339 var $attribute = {};
340 $attribute[i] = settings.series[j][i];
341 $.extend($attributes, $attribute);
342 }
343 }
344
345 function format_datum(datum, format, type){
346 if(format === '' || format === null || typeof format === 'undefined'){
347 return datum;
348 }
349 // if there is no type, this is probably coming from the axes formatting.
350 var removeDollar = true;
351 if(typeof type === 'undefined' || type === null){
352 // we will determine type on the basis of the presence or absence of #.
353 type = 'date';
354 if(format.indexOf('#') !== -1){
355 type = 'number';
356 }
357 removeDollar = false;
358 }
359
360 switch(type) {
361 case 'number':
362 // numeral.js works on 0 instead of # so we just replace that in the ICU pattern set.
363 format = format.replace(/#/g, '0');
364 // we also replace all instance of '$' as that is more relevant for ticks.
365 if(removeDollar){
366 format = format.replace(/\$/g, '');
367 }
368 datum = numeral(datum).format(format);
369 break;
370 case 'date':
371 case 'datetime':
372 case 'timeofday':
373 datum = moment(datum).format(format);
374 break;
375 }
376 return datum;
377 }
378
379 function format_data(datum, j, settings, series){
380 j = j - 1;
381 var format = typeof settings.series !== 'undefined' && typeof settings.series[j] !== 'undefined' ? settings.series[j].format : '';
382 return format_datum(datum, format, series[j + 1].type);
383 }
384
385 function override(settings, chart) {
386 if (settings.manual) {
387 try{
388 var options = JSON.parse(settings.manual);
389 $.extend(settings, options);
390 delete settings.manual;
391 }catch(error){
392 console.error("Error while adding manual configuration override " + settings.manual);
393 }
394 }
395 }
396
397
398 function render(v) {
399 for (var id in (all_charts || {})) {
400 renderChart(id, v);
401 }
402 }
403
404 $('body').on('visualizer:render:chart:start', function(event, v){
405 all_charts = v.charts;
406
407 if(v.is_front == true && typeof v.id !== 'undefined'){ // jshint ignore:line
408 renderChart(v.id, v);
409 } else {
410 render(v);
411 }
412
413 // for some reason this needs to be introduced here for dynamic preview updates to work.
414 v.update = function(){
415 renderChart('canvas', v);
416 };
417
418 });
419
420 $('body').on('visualizer:render:specificchart:start', function(event, v){
421 renderSpecificChart(v.id, v.chart, v.v);
422 });
423
424 $('body').on('visualizer:render:currentchart:update', function(event, v){
425 var data = v || event.detail;
426 renderChart('canvas', data.visualizer);
427 });
428
429 // front end actions
430 // 'image' is also called from the library
431 $('body').on('visualizer:action:specificchart', function(event, v){
432 var id = v.id;
433 if(typeof rendered_charts[id] === 'undefined'){
434 return;
435 }
436 var canvas = $('#' + id + ' canvas');
437 switch(v.action){
438 case 'print':
439 var win = window.open();
440 win.document.write("<br><img src='" + canvas[0].toDataURL() + "'/>");
441 win.document.close();
442 win.onload = function () { win.print(); setTimeout(win.close, 500); };
443 break;
444 case 'image':
445 var img = canvas[0].toDataURL();
446 if(img !== ''){
447 var $a = $("<a>"); // jshint ignore:line
448 $a.attr("href", img);
449 $("body").append($a);
450 $a.attr("download", v.dataObj.name);
451 $a[0].click();
452 $a.remove();
453 }else{
454 console.warn("No image generated");
455 }
456 break;
457 }
458 });
459
460 })(jQuery);
461
462
463