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

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