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  });