1/* global google */ 2/* global visualizer */ 3/* global console */ 4 5// this will store the images for each chart rendered. 6var __visualizer_chart_images = []; 7var chartWrapperError = []; 8var 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']
175== '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);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.