github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/dashboard-charts.js (about) 1 /* 2 Copyright 2023. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 var lineChart; 18 var pieOptions, barOptions; 19 20 function loadBarOptions(xAxisData, yAxisData) { 21 // colors for dark & light modes 22 let root = document.querySelector(':root'); 23 let rootStyles = getComputedStyle(root); 24 let gridLineDarkThemeColor = rootStyles.getPropertyValue('--black-3'); 25 let gridLineLightThemeColor = rootStyles.getPropertyValue('--white-3'); 26 let labelDarkThemeColor = rootStyles.getPropertyValue('--white-0'); 27 let labelLightThemeColor = rootStyles.getPropertyValue('--black-1') 28 29 barOptions = { 30 xAxis: { 31 type: 'category', 32 data: xAxisData, 33 axisLine: { 34 lineStyle: { 35 color: gridLineDarkThemeColor, 36 } 37 }, 38 axisLabel: { 39 interval: 0, // Set this to 0 to display all labels 40 rotate: 45, // You can adjust this value to rotate the labels 41 margin: 10, // You can adjust this value to add or reduce spacing between the labels and the axis line 42 color: function () { 43 return $('body').attr('data-theme') == 'dark' ? labelDarkThemeColor : labelLightThemeColor; 44 } 45 } 46 }, 47 yAxis: { 48 type: 'value', 49 axisLabel: { 50 color: function () { 51 return $('body').attr('data-theme') == 'dark' ? labelDarkThemeColor : labelLightThemeColor; 52 } 53 }, 54 splitLine: { 55 lineStyle: { 56 color: gridLineDarkThemeColor, 57 }, 58 }, 59 }, 60 series: [ 61 { 62 data: yAxisData, 63 type: 'bar', 64 barWidth: 10, 65 itemStyle: { 66 // color: '#6347D9' // You can set any color code here 67 color: function (params) { 68 var colorList = ['#6347D9', '#FF8700']; // Define an array of colors 69 return colorList[params.dataIndex % colorList.length]; // Use the modulus operator to alternate between colors 70 } 71 }, 72 barCategoryGap: '10%', // You can adjust this value to add space between bars 73 barGap: '15%', 74 tooltip: { 75 trigger: 'axis', // Set the trigger type for the tooltip 76 axisPointer: { // Set the type of pointer that shows up when hovering over the bars 77 type: 'shadow' // 'line' or 'shadow' for vertical or horizontal lines, respectively 78 }, 79 formatter: function (params) { // Add a formatter function to show the value of the bar in the tooltip 80 return params[0].name + ': ' + params[0].value; 81 } 82 }, 83 emphasis: { 84 itemStyle: { 85 color: '#FFC107' // Set the color of the bars when hovering over them 86 }, 87 label: { 88 show: true, 89 position: 'top', 90 formatter: function (params) { // Add a formatter function to show the value of the bar on hover 91 return params.value; 92 } 93 } 94 } 95 }, 96 97 ] 98 } 99 if ($('body').attr('data-theme') == "dark") { 100 barOptions.xAxis.axisLine.lineStyle.color = gridLineDarkThemeColor; 101 barOptions.yAxis.splitLine.lineStyle.color = gridLineDarkThemeColor; 102 } else { 103 barOptions.xAxis.axisLine.lineStyle.color = gridLineLightThemeColor; 104 barOptions.yAxis.splitLine.lineStyle.color = gridLineLightThemeColor; 105 } 106 } 107 function loadPieOptions(xAxisData, yAxisData) { 108 let pieDataMapList = []; 109 // loop 110 for (let i = 0; i < xAxisData.length; i++) { 111 let mapVal1 = yAxisData[i]; 112 let mapVal2 = xAxisData[i]; 113 let pieDataMap = {}; 114 pieDataMap['value'] = mapVal1; 115 pieDataMap['name'] = mapVal2; 116 pieDataMapList.push(pieDataMap); 117 } 118 let root = document.querySelector(':root'); 119 let rootStyles = getComputedStyle(root); 120 let labelDarkThemeColor = rootStyles.getPropertyValue('--white-0'); 121 let labelLightThemeColor = rootStyles.getPropertyValue('--black-1') 122 123 pieOptions = { 124 xAxis: { 125 show: false 126 }, 127 tooltip: { 128 trigger: 'item', 129 formatter: "{a} <br/>{b} : {c} ({d}%)" 130 }, 131 legend: { 132 orient: 'vertical', 133 left: 'left', 134 data: xAxisData, 135 textStyle: { 136 color: labelDarkThemeColor, 137 borderColor: labelDarkThemeColor, 138 139 }, 140 }, 141 series: [ 142 { 143 name: 'Pie Chart', 144 type: 'pie', 145 radius: '55%', 146 center: ['50%', '60%'], 147 data: pieDataMapList, 148 itemStyle: { 149 emphasis: { 150 shadowBlur: 10, 151 shadowOffsetX: 0, 152 shadowColor: 'rgba(0, 0, 0, 0.5)' 153 } 154 }, 155 label: { 156 color: labelDarkThemeColor, 157 } 158 } 159 ] 160 } 161 if ($('body').attr('data-theme') == "dark") { 162 pieOptions.series[0].label.color = labelDarkThemeColor; 163 pieOptions.legend.textStyle.borderColor = labelDarkThemeColor; 164 pieOptions.legend.textStyle.borderColor = labelDarkThemeColor; 165 } else { 166 pieOptions.series[0].label.color = labelLightThemeColor; 167 pieOptions.legend.textStyle.color = labelLightThemeColor; 168 pieOptions.legend.textStyle.borderColor = labelDarkThemeColor; 169 } 170 171 } 172 function renderBarChart(columns, hits, panelId, chartType, dataType, panelIndex) { 173 $(".panelDisplay #panelLogResultsGrid").hide(); 174 $(".panelDisplay #empty-response").empty(); 175 $('.panelDisplay #corner-popup').hide(); 176 $(".panelDisplay #empty-response").hide(); 177 $(`.panelDisplay .big-number-display-container`).empty(); 178 $(`.panelDisplay .big-number-display-container`).hide(); 179 $('.panelDisplay .panEdit-panel').empty(); 180 let bigNumVal = null; 181 let xAxisData = []; 182 let yAxisData = []; 183 184 if (columns.length == 1) { 185 bigNumVal = hits[0].MeasureVal[columns[0]]; 186 } 187 188 // loop through the hits and create the data for the bar chart 189 for (let i = 0; i < hits.length; i++) { 190 let hit = hits[i]; 191 192 let xAxisValue = hit.GroupByValues[0]; 193 let yAxisValue; 194 let measureVal = hit.MeasureVal; 195 yAxisValue = measureVal[columns[1]] 196 xAxisData.push(xAxisValue); 197 yAxisData.push(yAxisValue); 198 } 199 200 let panelChart; 201 if (panelId == -1) { 202 let panelChartEl = document.querySelector(`.panelDisplay .panEdit-panel`); 203 panelChart = echarts.init(panelChartEl); 204 } else if(chartType !== 'number'){ 205 let panelChartEl = $(`#panel${panelId} .panEdit-panel`); 206 panelChartEl.css("width", "100%").css("height", "100%"); 207 panelChart = echarts.init(document.querySelector(`#panel${panelId} .panEdit-panel`)); 208 } 209 210 if (bigNumVal != null) { 211 chartType = 'number'; 212 } 213 214 switch (chartType) { 215 case 'Bar Chart': 216 $(`.panelDisplay .big-number-display-container`).hide(); 217 loadBarOptions(xAxisData, yAxisData); 218 panelChart.setOption(barOptions); 219 break; 220 case 'Pie Chart': 221 $(`.panelDisplay .big-number-display-container`).hide(); 222 loadPieOptions(xAxisData, yAxisData); 223 panelChart.setOption(pieOptions); 224 break; 225 case 'number': 226 displayBigNumber(bigNumVal, panelId, dataType, panelIndex); 227 } 228 $(`#panel${panelId} .panel-body #panel-loading`).hide(); 229 230 return panelChart; 231 } 232 let mapIndexToAbbrev = new Map([ 233 ["", ""], 234 ["none", ""], 235 ["percent(0-100)", "%"], 236 ["bytes", "B"], 237 ["kB", "KB"], 238 ["MB", "MB"], 239 ["GB", "GB"], 240 ["TB", "TB"], 241 ["PB", "PB"], 242 ["EB", "EB"], 243 ["ZB", "ZB"], 244 ["YB", "YB"], 245 ["counts/sec", "c/s"], 246 ["writes/sec", "wr/s"], 247 ["reads/sec", "rd/s"], 248 ["requests/sec", "req/s"], 249 ["ops/sec", "ops/s"], 250 ["hertz(1/s)", "Hz"], 251 ["nanoseconds(ns)", "ns"], 252 ["microsecond(µs)", "µs"], 253 ["milliseconds(ms)", "ms"], 254 ["seconds(s)", "s"], 255 ["minutes(m)", "m"], 256 ["hours(h)", "h"], 257 ["days(d)", "d"], 258 ["packets/sec", "p/s"], 259 ["bytes/sec", "B/s"], 260 ["bits/sec", "b/s"], 261 ["kilobytes/sec", "KB/s"], 262 ["kilobits/sec", "Kb/s"], 263 ["megabytes/sec", "MB/s"], 264 ["megabits/sec", "Mb/s"], 265 ["gigabytes/sec", "GB/s"], 266 ["gigabits/sec", "Gb/s"], 267 ["terabytes/sec", "TB/s"], 268 ["terabits/sec", "Tb/s"], 269 ["petabytes/sec", "PB/s"], 270 ["petabits/sec", "Pb/s"], 271 ]) 272 273 function addSuffix(number) { 274 let suffix = ''; 275 if (number >= 1e24) { 276 suffix = "Y"; 277 number /= 1e24; 278 } else if (number >= 1e21) { 279 suffix = "Z"; 280 number /= 1e21; 281 } else if (number >= 1e18) { 282 suffix = "E"; 283 number /= 1e18; 284 } else if (number >= 1e15) { 285 suffix = "P"; 286 number /= 1e15; 287 } else if (number >= 1e12) { 288 suffix = "T"; 289 number /= 1e12; 290 } else if (number >= 1e9) { 291 suffix = "G"; 292 number /= 1e9; 293 } else if (number >= 1e6) { 294 suffix = "M"; 295 number /= 1e6; 296 } 297 298 return [number.toFixed(2), suffix]; 299 } 300 301 function findSmallestGreaterOne(number) { 302 const hours = number / 3600; // Convert seconds to hours 303 const minutes = number / 60; // Convert seconds to minutes 304 const years = number / (3600 * 24 * 365); // Convert seconds to years 305 306 let smallest = Infinity; 307 let suffix = ''; 308 309 if (hours > 1 && hours < smallest) { 310 smallest = hours; 311 suffix = 'h'; 312 } 313 if (minutes > 1 && minutes < smallest) { 314 smallest = minutes; 315 suffix = 'm'; 316 } 317 if (years > 1 && years < smallest) { 318 smallest = years; 319 suffix = 'year'; 320 } 321 322 if (smallest === Infinity) { 323 smallest = number; 324 suffix = 's'; 325 } 326 327 return [smallest.toFixed(2),suffix]; 328 } 329 330 function displayBigNumber(value, panelId, dataType, panelIndex) { 331 if (panelId === -1) { 332 $('.panelDisplay .panEdit-panel').hide(); 333 $(`.panelDisplay .big-number-display-container`).show(); 334 $(`.panelDisplay .big-number-display-container`).empty(); 335 } else { 336 $(`#panel${panelId} .panEdit-panel`).hide(); 337 $(`#panel${panelId} .big-number-display-container`).show(); 338 $(`#panel${panelId} .big-number-display-container`).empty(); 339 } 340 341 let panelChartEl; 342 343 if (panelId == -1) { 344 panelChartEl = $('.panelDisplay .big-number-display-container'); 345 } else { 346 panelChartEl = $(`#panel${panelId} .big-number-display-container`); 347 $(`#panel${panelId} .panEdit-panel`).hide(); 348 panelChartEl = $(`#panel${panelId} .big-number-display-container`); 349 panelChartEl.css("width", "100%").css("height", "100%"); 350 } 351 352 if (!value) { 353 panelChartEl.append(`<div class="big-number">NA</div> `); 354 } else { 355 if (dataType != null && dataType != undefined && dataType != ''){ 356 var bigNum = []; 357 let dataTypeAbbrev = mapIndexToAbbrev.get(dataType); 358 let number = parseFloat(value.replace(/,/g, '')); 359 let dataTypeAbbrevCap = dataTypeAbbrev.substring(0,2).toUpperCase() 360 if (["KB",'MB',"GB","TB","PB","EB","ZB","YB"].includes(dataTypeAbbrevCap)){ 361 dataTypeAbbrev = dataTypeAbbrev.substring(1) 362 if(dataTypeAbbrevCap === "KB"){ 363 number = number * 1000; 364 }else if (dataTypeAbbrevCap === "MB"){ 365 number = number * 1e6; 366 } else if(dataTypeAbbrevCap === "GB"){ 367 number = number * 1e9; 368 } else if(dataTypeAbbrevCap === "TB"){ 369 number = number * 1e12; 370 } else if(dataTypeAbbrevCap === "PB"){ 371 number = number * 1e15; 372 } else if (dataTypeAbbrevCap === "EB") { 373 number *= 1e18; 374 } else if (dataTypeAbbrevCap === "ZB") { 375 number *= 1e21; 376 } else if (dataTypeAbbrevCap === "YB") { 377 number *= 1e24; 378 } 379 bigNum = addSuffix(number); 380 381 } else if(['ns',"µs", "ms", "d","m","s","h"].includes(dataTypeAbbrev)){ 382 if (dataTypeAbbrev === "ns"){ 383 number = number / 1e9; 384 } else if (dataTypeAbbrev === "µs"){ 385 number = number / 1e6; 386 }else if (dataTypeAbbrev === "ms"){ 387 number = number / 1e3; 388 }else if (dataTypeAbbrev === "d"){ 389 number = number * 24 * 60 * 60 390 }else if (dataTypeAbbrev === "m"){ 391 number = number * 60 392 }else if (dataTypeAbbrev === "s"){ 393 number = number 394 }else if (dataTypeAbbrev === "h"){ 395 number = number * 3600; 396 } 397 dataTypeAbbrev = "" 398 bigNum = findSmallestGreaterOne(number); 399 } else if (dataTypeAbbrev === "" || dataTypeAbbrev === "%"){ 400 bigNum[1] = "" 401 bigNum[0] = number 402 } else{ 403 bigNum = addSuffix(number); 404 } 405 panelChartEl.append(`<div class="big-number">${bigNum[0]} </div> <div class="unit">${bigNum[1]+dataTypeAbbrev} </div> `); 406 }else{ 407 panelChartEl.append(`<div class="big-number">${value} </div> `); 408 } 409 } 410 411 412 if(panelId === -1) { 413 let parentWidth = $(".panelDisplay").width(); 414 let numWidth = $(".panelDisplay .big-number-display-container").width(); 415 if (numWidth > parentWidth){ 416 $(".big-number").css("font-size","5.5em"); 417 $(".unit").css("font-size","55px"); 418 } 419 } 420 if(panelId !== -1) { 421 resizePanelFontSize(panelIndex, panelId); 422 } 423 } 424 425 function createColorsArray() { 426 let root = document.querySelector(':root'); 427 let rootStyles = getComputedStyle(root); 428 let colorArray = []; 429 for (let i = 1; i <= 20; i++) { 430 colorArray.push(rootStyles.getPropertyValue(`--graph-line-color-${i}`)); 431 } 432 return colorArray; 433 } 434 435 function renderLineChart(seriesArray, metricsDatasets, labels, panelId, chartType, flag) { 436 const colors = createColorsArray(); 437 let gridLineColor; 438 let tickColor; 439 let root = document.querySelector(':root'); 440 let rootStyles = getComputedStyle(root); 441 let gridLineDarkThemeColor = rootStyles.getPropertyValue('--black-3'); 442 let gridLineLightThemeColor = rootStyles.getPropertyValue('--white-3'); 443 let tickDarkThemeColor = rootStyles.getPropertyValue('--white-0'); 444 let tickLightThemeColor = rootStyles.getPropertyValue('--white-6'); 445 446 if ($('body').attr('data-theme') == "light") { 447 gridLineColor = gridLineLightThemeColor; 448 tickColor = tickLightThemeColor; 449 } 450 else { 451 gridLineColor = gridLineDarkThemeColor; 452 tickColor = tickDarkThemeColor; 453 } 454 let datasets = []; 455 let data = {}; 456 457 datasets = seriesArray.map((o, i) => ({ 458 label: o.seriesName, 459 data: Object.values(o.values), 460 backgroundColor: colors[i], 461 borderColor: colors[i], 462 color: gridLineColor, 463 borderWidth: 2, 464 fill: false, 465 })); 466 data = { 467 labels: labels, 468 datasets: datasets 469 } 470 471 const config = { 472 type: 'line', 473 data, 474 options: { 475 maintainAspectRatio: false, 476 responsive: true, 477 title: { 478 display: true, 479 }, 480 plugins: { 481 legend: { 482 display: false 483 }, 484 tooltip: { 485 enabled: true, 486 } 487 }, 488 scales: { 489 x: { 490 grid: { 491 color: function () { 492 return $('body').attr('data-theme') == 'dark' ? gridLineDarkThemeColor : gridLineLightThemeColor; 493 }, 494 }, 495 ticks: { 496 color: function () { 497 return $('body').attr('data-theme') == 'dark' ? tickDarkThemeColor : tickLightThemeColor; 498 } 499 }, 500 }, 501 y: { 502 grid: { 503 color: function () { 504 return $('body').attr('data-theme') == 'dark' ? gridLineDarkThemeColor : gridLineLightThemeColor; 505 }, 506 }, 507 ticks: { 508 color: function () { 509 return $('body').attr('data-theme') == 'dark' ? tickDarkThemeColor : tickLightThemeColor; 510 }, 511 }, 512 } 513 } 514 } 515 } 516 if (panelId == -1) { 517 var panelChartEl = $(`.panelDisplay .panEdit-panel`); 518 } else { 519 var panelChartEl = $(`#panel${panelId} .panEdit-panel`); 520 panelChartEl.css("width", "100%").css("height", "100%"); 521 } 522 523 if (flag === -1) { 524 let can = `<canvas class="line-chart-canvas" ></canvas>` 525 panelChartEl.append(can) 526 var lineCanvas = (panelChartEl).find('canvas')[0].getContext('2d'); 527 lineChart = new Chart(lineCanvas, config); 528 $(`#panel${panelId} .panel-body #panel-loading`).hide(); 529 if (panelId === -1){ 530 panelChartEl.append(`<div class="lineChartLegend"></div>`) 531 displayLegendsForLineChart(seriesArray, labels, colors,metricsDatasets,panelId, chartType); 532 } 533 } 534 535 const bgColor = []; 536 const bColor = data.datasets.map(color => { 537 bgColor.push(color.backgroundColor); 538 return color.borderColor; 539 }) 540 541 if (flag == -2) { 542 543 lineChart.config.data.datasets.map((dataset, index) => { 544 dataset.backgroundColor = bgColor[index]; 545 dataset.borderColor = bColor[index]; 546 }) 547 lineChart.update(); 548 } 549 550 if(flag >= 0) { 551 const chartDataObject = lineChart.config.data.datasets.map(dataset => { 552 dataset.borderColor = "rgba(0,0,0,0)"; 553 dataset.backgroundColor = "rgba(0,0,0,0)"; 554 }) 555 556 lineChart.config.data.datasets[flag].borderColor = bColor[flag]; 557 lineChart.config.data.datasets[flag].backgroundColor = bgColor[flag]; 558 lineChart.update(); 559 } 560 561 return lineChart; 562 }; 563 564 function displayLegendsForLineChart(seriesArray, labels, colors,metricsDatasets,panelId, chartType) { 565 $.each(seriesArray, function (k, v) { 566 const htmlString = `<div class="legend-element-line" id="legend-line-${k}"><span class="legend-colors-line" style="background-color:` + colors[k] + `"></span>` + v.seriesName + `</div>`; 567 $('.lineChartLegend').append(htmlString); 568 }); 569 570 let prev = null; 571 572 const legends = document.querySelectorAll('.legend-element-line'); 573 $.each(legends, function (i, legend) { 574 legend.addEventListener('click', (e) => { 575 let currSelectedEl = parseInt((e.target.id).slice(12)); 576 if(prev == null) { 577 e.target.classList.add("selected"); 578 prev = currSelectedEl; 579 renderLineChart(seriesArray, metricsDatasets, labels, panelId, chartType, currSelectedEl); 580 } else if (prev == currSelectedEl) { 581 e.target.classList.remove("selected"); 582 prev = null; 583 renderLineChart(seriesArray, metricsDatasets, labels, panelId, chartType, -2); 584 } else { 585 let prevEl = document.getElementById(`legend-line-${prev}`); 586 prevEl.classList.remove("selected"); 587 e.target.classList.add("selected"); 588 prev = currSelectedEl; 589 renderLineChart(seriesArray, metricsDatasets, labels, panelId, chartType, currSelectedEl); 590 } 591 } 592 ) 593 }) 594 } 595 596 window.addEventListener('resize', function (event) { 597 if ($('.panelEditor-container').css('display') !== 'none' && panelChart){ 598 panelChart.resize(); 599 } 600 });