PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.7
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.7
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-google.js

render-google.js in Visualizer – Tables & Charts Manager with Built-in AI Generator 4.0.7, at js/render-google.js

680 lines 25.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global google */
2 /* global visualizer */
3 /* global console */
4
5 // this will store the images for each chart rendered.
6 var __visualizer_chart_images = [];
7 var chartWrapperError = [];
8 var isResizeRequest = false;
9
10 (function($) {
11 var gv;
12 var all_charts, objects;
13 // so that we know which charts belong to our library.
14 var rendered_charts = [];
15
16 function renderChart(id) {
17
18 if ( ! all_charts || 0 === Object.keys( all_charts ).length ) {
19 return;
20 }
21
22 var chart = all_charts[id];
23 var hasAnnotation = false;
24
25 if ( ! chart ) {
26 return;
27 }
28
29 if ( chart.library && chart.library !== 'google' && chart.library !== 'GoogleCharts' ) {
30 return;
31 }
32
33 // re-render the chart only if it doesn't have annotations and it is on the front-end
34 // this is to prevent the chart from showing "All series on a given axis must be of the same data type" during resize.
35 // remember, some charts do not support annotations so they should not be included in this.
36 var no_annotation_charts = ['tabular', 'timeline', 'gauge', 'geo', 'bubble', 'candlestick'];
37 if ( undefined !== chart.settings && undefined !== chart.settings.series && undefined === chart.settings.series.length ) {
38 var seriesKeys = Object.keys( chart.settings.series );
39 // Only convert when keys are numeric indices (PHP JSON-encoded array).
40 // String keys (e.g. named series from manual config) must be left as-is.
41 if ( seriesKeys.every( function( k ) { return ! isNaN( k ); } ) ) {
42 var chartSeries = [];
43 var chartSeriesValue = Object.values( chart.settings.series );
44 $.each( seriesKeys, function( index, element ) {
45 chartSeries[ element ] = chartSeriesValue[ index ];
46 } );
47 chart.settings.series = chartSeries;
48 }
49 }
50 if(id !== 'canvas' && typeof chart.series !== 'undefined' && typeof chart.settings.series !== 'undefined' && ! no_annotation_charts.includes(chart.type) ) {
51 hasAnnotation = chart.series.length - chart.settings.series.length > 1;
52 }
53
54 if(! hasAnnotation){
55 renderSpecificChart(id, all_charts[id]);
56 }
57 }
58
59 function renderSpecificChart(id, chart) {
60 var render, container, series, data, table, settings, i, j, row, date, axis, property, format, formatter;
61
62 if( chart.library !== 'google' ) {
63 return;
64 }
65
66 // Bail if the chart is already rendered or is being rendered.
67 if ( ! window.isResizeRequest && ( $('#' + id).hasClass('visualizer-chart-loaded') || ( 'canvas' !== id && $('#' + id).children( ':not(.loader, style)' ).length > 0 ) ) ) {
68 return;
69 }
70 rendered_charts[id] = 'yes';
71
72 series = chart.series;
73 data = chart.data;
74 settings = chart.settings;
75
76 container = document.getElementById(id);
77 if (container == null) {
78 return;
79 }
80 table = new gv.DataTable({cols: series});
81
82 render = objects[id] || null;
83 if (!render) {
84 switch (chart.type) {
85 case "tabular":
86 render = "Table";
87 break;
88 case "gauge":
89 case "table":
90 case "timeline":
91 render = chart.type.charAt(0).toUpperCase() + chart.type.slice(1);
92 break;
93 default:
94 render = chart.type.charAt(0).toUpperCase() + chart.type.slice(1) + 'Chart';
95 break;
96 }
97
98 var controlWrapperElement = document.getElementById( "control_wrapper_" + id ) || null;
99 var withControlMode = ( typeof settings.controls !== 'undefined' && '' !== settings.controls.controlType ) && controlWrapperElement ? true : false;
100
101 if ( $( controlWrapperElement ).hasClass( 'no-filter' ) ) {
102 withControlMode = false;
103 }
104 if ( withControlMode ) {
105 // Chart wrapper.
106 render = new gv.ChartWrapper({
107 'chartType': render,
108 'containerId': id,
109 });
110 // Chart dashboard wrapper.
111 var chartWrapperElement = document.getElementById( "chart_wrapper_" + id ) || null;
112 var chartWrapper = new gv.Dashboard( chartWrapperElement );
113
114 // Error Handler.
115 gv.events.addListener(chartWrapper, 'error', function ( err ) {
116 if ( chartWrapperError.length === 0 ) {
117 chartWrapperError.push( err );
118 gv.errors.removeError( err.id );
119 var chartContainer = $( chartWrapperElement ).find( '.visualizer-front' );
120 if ( chartContainer && chartContainer.is(':visible')) {
121 if (chartContainer.parents('div').next( '#sidebar' ).length === 0) {
122 chartContainer.addClass( 'visualizer-chart-loaded' ).addClass( 'visualizer-cw-error' );
123 $( chartWrapperElement ).addClass( 'visualizer-cw-error' );
124 }
125 }
126 } else {
127 gv.errors.removeError( err.id );
128 }
129 } );
130 } else {
131 render = new gv[render](container);
132 }
133 }
134
135 if (settings['animation'] && parseInt(settings['animation']['startup']) === 1)
136 {
137 settings['animation']['startup'] = true;
138 settings['animation']['duration'] = parseInt(settings['animation']['duration']);
139 }
140 if ( settings['controls'] ) {
141 settings['controls']['ui']['allowMultiple'] = 'true' === settings['controls']['allowMultiple'] ? true : false;
142 settings['controls']['ui']['allowTyping'] = 'true' === settings['controls']['allowTyping'] ? true : false;
143 settings['controls']['ui']['showRangeValues'] = 'true' === settings['controls']['showRangeValues'] ? true : false;
144 }
145
146 // mark roles for series that have specified a role
147 // and then remove them from future processing
148 // and also adjust the indices of the series array so that
149 // the ones with a role are ignored
150 // e.g. if there are 6 columns (0-5) out of which 1, 3 and 5 are annotations
151 // the final series will only include 0, 2, 4 (reindexed as 0, 1, 2)
152
153 // this will capture all the series indexes that became annotations.
154 var series_annotations = [];
155 if (settings.series) {
156 var adjusted_series = [];
157 for (i = 0; i < settings.series.length; i++) {
158 if (!series[i + 1] || typeof settings.series[i] === 'undefined') {
159 continue;
160 }
161 if(typeof settings.series[i].role !== 'undefined'){
162 table.setColumnProperty(i + 1, 'role', settings.series[i].role);
163 if(settings.series[i].role === '') {
164 adjusted_series.push(settings.series[i]);
165 }else{
166 series_annotations.push(i);
167 }
168 }
169 }
170 if(adjusted_series.length > 0){
171 settings.series = adjusted_series;
172 }
173 }
174
175 if ( settings['explorer_enabled'] && settings['explorer_enabled'] == 'true' ) { // jshint ignore:line
176 var $explorer = {};
177 $explorer['keepInBounds'] = true;
178
179 if ( settings['explorer_actions'] ) {
180 $explorer['actions'] = settings['explorer_actions'];
181 }
182 settings['explorer'] = $explorer;
183 }
184
185 switch (chart.type) {
186 case 'pie':
187 if (settings.slices) {
188 for (i in settings.slices) {
189 if (settings.slices[i]['color'] === '') {
190 delete settings.slices[i]['color'];
191 }
192 }
193 }
194 break;
195 case 'line':
196 case 'bar':
197 case 'column':
198 case 'area':
199 case 'scatter':
200 case 'candlestick':
201 if (settings.series) {
202 for (i in settings.series) {
203 if (settings.series[i]['color'] === '') {
204 delete settings.series[i]['color'];
205 }
206 }
207 }
208
209 if (settings.series && settings.legend && settings.legend.position === "left")
210 {
211 settings.targetAxisIndex = 1;
212 }
213 break;
214 case 'geo':
215 if (settings.region !== undefined && settings.region.replace(/^\s+|\s+$/g, '') === '') {
216 settings['region'] = 'world';
217 }
218 break;
219 case 'table':
220 case 'tabular':
221 if (parseInt(settings['pagination']) !== 1)
222 {
223 delete settings['pageSize'];
224 }
225 break;
226 case 'gauge':
227 break;
228 case 'bubble':
229 settings.sortBubblesBySize = settings.sortBubblesBySize ? settings.sortBubblesBySize == 1 : false; // jshint ignore:line
230 break;
231 case 'timeline':
232 settings['timeline'] = {};
233 settings['timeline']['groupByRowLabel'] = settings['groupByRowLabel'] ? true : false;
234 settings['timeline']['colorByRowLabel'] = settings['colorByRowLabel'] ? true : false;
235 settings['timeline']['showRowLabels'] = settings['showRowLabels'] ? true : false;
236 if(settings['singleColor'] !== '') {
237 settings['timeline']['singleColor'] = settings['singleColor'];
238 }
239 break;
240 case 'combo':
241 if (settings.series) {
242 for (i in settings.series) {
243 if (settings.series[i]['type'] === '') {
244 delete settings.series[i]['type'];
245 }
246 if (settings.series[i]['color'] === '') {
247 delete settings.series[i]['color'];
248 }
249 }
250 }
251
252 if (settings.series && settings.legend && settings.legend.position === "left")
253 {
254 settings.targetAxisIndex = 1;
255 }
256 break;
257 default:
258 return;
259 }
260
261 if (series[0] && (series[0].type === 'date' || series[0].type === 'datetime' || series[0].type === 'timeofday')) {
262 axis = false;
263 switch (chart.type) {
264 case 'line':
265 case 'area':
266 case 'scatter':
267 case 'candlestick':
268 case 'column':
269 case 'combo':
270 axis = settings.hAxis;
271 break;
272 case 'bar':
273 axis = settings.vAxis;
274 break;
275 }
276
277 if (axis) {
278 for (property in axis.viewWindow) {
279 date = new Date(axis.viewWindow[property]);
280 if (Object.prototype.toString.call(date) === "[object Date]") {
281 if (!isNaN(date.getTime())) {
282 axis.viewWindow[property] = date;
283 continue;
284 }
285 }
286
287 delete axis.viewWindow[property];
288 }
289 }
290
291 if(settings.hAxis && settings.hAxis.format == ''){
292 settings.hAxis.format = 'yyyy-MM-dd';
293 }
294 }
295
296 if(settings.hAxis){
297 if(settings.hAxis.textStyle && settings.hAxis.textStyle !== ''){
298 if(typeof(settings.hAxis.textStyle) === "string") {
299 settings.hAxis.textStyle = {color: settings.hAxis.textStyle};
300 }
301 }
302 }
303
304 if(settings.vAxis){
305 if(settings.vAxis.textStyle && settings.vAxis.textStyle !== ''){
306 if(typeof(settings.vAxis.textStyle) === "string") {
307 settings.vAxis.textStyle = {color: settings.vAxis.textStyle};
308 }
309 }
310 }
311
312 for (i = 0; i < data.length; i++) {
313 row = [];
314 for (j = 0; j < series.length; j++) {
315 switch(series[j].type) {
316 case 'string':
317 if(data[i][j] === ''){
318 data[i][j] = null;
319 }
320 break;
321 case 'boolean':
322 if (typeof data[i][j] === 'string'){
323 data[i][j] = data[i][j] === 'true';
324 }
325 break;
326 case 'date':
327 // fall-through.
328 case 'datetime':
329 if (typeof data[i][j] === 'string') {
330 var dateParse = Date.parse(data[i][j].replace(/-/g, '/'));
331 if ( isNaN( dateParse ) ) {
332 dateParse = Date.parse(data[i][j]);
333 }
334 date = new Date( dateParse );
335 } else if(typeof data[i][j] === 'object') {
336 date = data[i][j];
337 }
338 data[i][j] = null;
339 if (Object.prototype.toString.call(date) === "[object Date]") {
340 if (!isNaN(date.getTime())) {
341 data[i][j] = date;
342 }
343 }
344 break;
345 }
346 row.push(data[i][j]);
347 }
348 table.addRow(row);
349 }
350
351 if (settings.series) {
352 switch(chart.type){
353 case 'table':
354 case 'tabular':
355 for(i in settings.series){
356 i = parseInt(i);
357 if (!series[i + 1]) {
358 continue;
359 }
360 format_data(id, table, series[i + 1].type, settings.series[i].format, i + 1);
361 }
362 break;
363 default:
364 // Single-pass: walk columns, skip annotation/helper roles, apply formats in order.
365 var k = 0; // index into settings.series (visible series)
366 for (var c = 1; c < series.length && k < settings.series.length; c++) { // skip label at 0
367 if (table.getColumnProperty(c, 'role')) continue; // helper/annotation column
368 var s = settings.series[k++];
369 if (!s || !s.format) continue;
370 if (!series[c]) continue;
371 format_data(id, table, series[c].type, s.format, c);
372 }
373 break;
374 }
375 } else if (chart.type === 'pie' && settings.format && settings.format !== '') {
376 format_data(id, table, 'number', settings.format, 1);
377 }
378
379 if(settings.hAxis && series[0]) {
380 format_data(id, table, series[0].type, settings.hAxis.format, 0);
381 }
382
383 override(settings);
384
385 gv.events.addListener(render, 'ready', function () {
386 var arr = id.split('-');
387 render = typeof render.getChart !== 'undefined' ? render.getChart() : render;
388 __visualizer_chart_images[ arr[0] + '-' + arr[1] ] = '';
389
390 if (render.container && $(render.container).is(':visible')) {
391 if ($(render.container).parents('div').next( '#sidebar' ).length === 0) {
392 $(render.container).addClass( 'visualizer-chart-loaded' );
393 }
394 }
395
396 try{
397 if ( typeof render.getImageURI !== 'undefined' ) {
398 var img = render.getImageURI();
399 __visualizer_chart_images[ arr[0] + '-' + arr[1] ] = img;
400 $('body').trigger('visualizer:render:chart', {id: arr[1], image: img});
401 if ( $( '#chart-img' ).length ) {
402 $( '#chart-img' ).val( img );
403 }
404 }
405 }catch(error){
406 var canvas = document.getElementById( 'canvas' );
407 domtoimage.toPng(canvas)
408 .then(function ( img ) {
409 __visualizer_chart_images[ arr[0] + '-' + arr[1] ] = img;
410 $('body').trigger('visualizer:render:chart', {id: arr[1], image: img});
411 if ( $( '#chart-img' ).length ) {
412 $( '#chart-img' ).val( img );
413 }
414 })
415 .catch(function (error) {
416 console.warn('render.getImageURI not defined for ' + arr[0] + '-' + arr[1]);
417 });
418 }
419 });
420
421 if ( withControlMode ) {
422 // alert( chart.is_library_page );
423 // Create a control wrapper, passing some options.
424 var controlWrapper = new gv.ControlWrapper( {
425 containerId: 'control_wrapper_' + id,
426 controlType: settings.controls.controlType,
427 'options': settings.controls,
428 } );
429
430 $('body').trigger('visualizer:chart:settings:extend', {id: id, chart: chart, settings: settings, data: table});
431 render.setOptions(settings);
432 chartWrapper.bind(controlWrapper, render);
433 chartWrapper.draw(table);
434
435 gv.events.addListener(controlWrapper, 'ready', function ( err ) {
436 if ( 'vertical' === settings.controls.ui.orientation ) {
437 if ( 'canvas' === id ) {
438 jQuery( '#' + id ).addClass( 'vz-vertical' );
439 }
440 } else {
441 jQuery( '#' + id ).removeClass( 'vz-vertical' );
442 }
443 });
444 } else {
445 $('body').trigger('visualizer:chart:settings:extend', {id: id, chart: chart, settings: settings, data: table});
446 render.draw(table, settings);
447 }
448 }
449
450 function format_data(id, table, type, format, index) {
451 if (!format || format === '') {
452 return;
453 }
454
455 var formatter = null;
456 var $formatInput = $('input.control-text[name*="[format]"]').filter(function() {
457 return $(this).val() === format;
458 });
459 try {
460 switch (type) {
461 case 'number':
462 formatter = new gv.NumberFormat({pattern: format});
463 break;
464 case 'date':
465 case 'datetime':
466 case 'timeofday':
467 formatter = new gv.DateFormat({pattern: format});
468 break;
469 }
470
471 if (formatter) {
472 formatter.format(table, index);
473 $formatInput.nextAll('.visualizer-format-error').remove();
474 }
475 } catch (e) {
476 if ($formatInput.length) {
477 $formatInput.nextAll('.visualizer-format-error').remove();
478 $('<p class="visualizer-format-error" style="color:#cc0000;margin:4px 0 0"></p>').text(visualizer.l10n.invalid_format).insertAfter($formatInput);
479 }
480 }
481
482 var arr = id.split('-');
483 $('body').trigger('visualizer:format:chart', {id: parseInt(arr[1]), data: table, column: index});
484
485 }
486
487 function override(settings) {
488 if (settings.manual) {
489 try{
490 var options = JSON.parse(settings.manual);
491 $.extend(settings, options);
492 delete settings.manual;
493 }catch(error){
494 console.error("Error while adding manual configuration override " + settings.manual);
495 }
496 }
497 }
498
499 function render() {
500 for (var id in (all_charts || {})) {
501 var chartElement = document.getElementById( id );
502 if (chartElement && chartElement.offsetParent) {
503 renderChart(id);
504 }
505 }
506 }
507
508 var resizeTimeout;
509
510 $(document).ready(function() {
511 $(window).resize(function(e) {
512 clearTimeout(resizeTimeout);
513 window.isResizeRequest = 'undefined' !== typeof e.originalEvent ? true : false;
514 resizeTimeout = setTimeout(render, 100);
515 });
516
517 resizeHiddenContainers(true);
518
519 if ( $( '.visualizer-hidden-container' ).length ) {
520 setInterval( function() {
521 $( '.visualizer-hidden-container' ).find(".visualizer-front:not(.visualizer-chart-loaded)").resize();
522 }, 500 );
523 }
524
525 window.addEventListener( 'orientationchange', function() {
526 $( '.visualizer-chart-loaded' ).removeClass( 'visualizer-chart-loaded' ).resize();
527 }, false);
528 });
529
530 $(window).on('load', function(){
531 $( '.visualizer-front:not(.visualizer-chart-loaded)' ).resize();
532 resizeHiddenContainers(true);
533 });
534
535 function resizeHiddenContainers(everytime){
536 $(".visualizer-front").parents().each(function(){
537 if(!$(this).is(":visible") && !$(this).hasClass("visualizer-hidden-container")){
538 $(this).addClass("visualizer-hidden-container");
539 }
540 });
541
542 var mutateObserver = new MutationObserver(function(records) {
543 records.forEach(function(record) {
544 if(record.attributeName === "style" || record.attributeName === "class"){
545 var element = $(record.target);
546 var displayStyle = window.getComputedStyle(element[0]).getPropertyValue("display");
547 if(element.hasClass("visualizer-hidden-container-resized") || displayStyle === "none") { return ; }
548 element.find(".visualizer-front").resize();
549 if(!everytime) {
550 element.addClass("visualizer-hidden-container-resized");
551 }
552 }
553 });
554 });
555
556 $('.visualizer-hidden-container').each(function(){
557 mutateObserver.observe($(this)[0], {attributes: true});
558 });
559 }
560
561 $('body').on('visualizer:render:chart:start', function(event, v){
562 var $chart_types = ['corechart', 'geochart', 'gauge', 'table', 'timeline'];
563 if(v.is_front == true){ // jshint ignore:line
564 // check what all chart types to load.
565 $chart_types = [];
566 $.each(v.charts, function(i, c){
567 // Only consider charts rendered by Google Charts.
568 if ( c.library && c.library !== 'google' && c.library !== 'GoogleCharts' ) {
569 return;
570 }
571 var $type = c.type;
572 switch($type){
573 case 'bar':
574 case 'column':
575 case 'line':
576 case 'area':
577 case 'stepped area':
578 case 'bubble':
579 case 'pie':
580 case 'donut':
581 case 'combo':
582 case 'candlestick':
583 case 'histogram':
584 case 'scatter':
585 case 'bubble':
586 $type = 'corechart';
587 break;
588 case 'geo':
589 $type = 'geochart';
590 break;
591 case 'tabular':
592 case 'table':
593 $type = 'table';
594 break;
595 case 'dataTable':
596 case 'polarArea':
597 case 'radar':
598 $type = null;
599 break;
600 }
601 if($type != null && $type !== ''){
602 $chart_types.push($type);
603 }
604 });
605 }
606
607 objects = {};
608 if ( 'object' === typeof google ) {
609 $chart_types.push( 'controls' );
610 google.load( 'visualization', '51', {packages: $chart_types, mapsApiKey: v.map_api_key, 'language' : v.language,
611 callback: function () {
612 gv = google.visualization;
613 all_charts = v.charts;
614 if(v.is_front == true && typeof v.id !== 'undefined'){ // jshint ignore:line
615 if ( document.getElementById( v.id ).offsetParent !== null ) {
616 renderChart(v.id);
617 }
618 } else {
619 render();
620 }
621 }
622 });
623 }
624 });
625
626 $('body').on('visualizer:render:specificchart:start', function(event, v){
627 objects = {};
628 gv = google.visualization;
629 renderSpecificChart(v.id, v.chart);
630 });
631
632 $('body').on('visualizer:render:currentchart:update', function(event, v){
633 renderChart('canvas');
634 });
635
636 // front end actions
637 // 'image' is also called from the library
638 $('body').on('visualizer:action:specificchart', function(event, v){
639 var id = v.id;
640 if(typeof rendered_charts[id] === 'undefined'){
641 return;
642 }
643 var arr = id.split('-');
644 var img = __visualizer_chart_images[ arr[0] + '-' + arr[1] ];
645 switch(v.action){
646 case 'print':
647 // for charts that have no rendered image defined, we print the data instead.
648 var html = v.data;
649 if(img !== ''){
650 html = "<html><body><img src='" + img + "' /></body></html>";
651 }
652 $('body').trigger('visualizer:action:specificchart:defaultprint', {data: html});
653 break;
654 case 'image':
655 if(img !== ''){
656 var $a = $("<a>"); // jshint ignore:line
657 $a.attr("href", img);
658 $("body").append($a);
659 $a.attr("download", v.dataObj.name);
660 $a[0].click();
661 $a.remove();
662 }else{
663 console.warn("No image generated");
664 }
665 break;
666 }
667 });
668
669 var timer = 0;
670 $( document ).on( 'input', '.series-linewidth', function() {
671 var seriesLineWidth = $( this );
672 clearTimeout( timer );
673 timer = setTimeout( function() {
674 if ( seriesLineWidth.val() != '' && seriesLineWidth.val() <= 0 ) {
675 seriesLineWidth.val( '0.1' );
676 }
677 }, 700 );
678 } );
679 })(jQuery);
680