ShawLiu6's picture
download
raw
31.2 kB
/* exported echartsJenkinsApi */
const trendDefaultStorageId = 'jenkins-echarts-trend-configuration-default';
const echartsJenkinsApi = {
/**
* Resolves all Jenkins colors within the specified string model. These colors are specified as CSS variables
* of the form <code>--color-name</code>. The method replaces all occurrences of these variables with the actual
* color value of the current Jenkins theme.
*
* @param {String} model - the model to escape the Jenkins colors
* @returns {String} the escaped string
*/
resolveJenkinsColors: function (model) {
return model.replaceAll(/--([a-z-]+)/g, function (match) {
return echartsJenkinsApi.resolveJenkinsColor(match)
})
},
/**
* Returns the theme-aware color of all texts.
*
* @return {string|string}
*/
getTextColor: function () {
return echartsJenkinsApi.resolveJenkinsColor('--text-color');
},
/**
* Returns the theme-aware color of all texts.
*
* @return {string|string}
*/
resolveJenkinsColor: function (colorName) {
return getComputedStyle(document.body).getPropertyValue(colorName) || '#333';
},
/**
* Escapes the meta characters of the specified string so that the string can be used as an ID.
*
* @param {String} string - the string to escape
* @returns {String} the escaped string
*/
escapeMetaCharacters: function (string) {
return string.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&')
},
/**
* Reads the specified configuration from the local storage.
*
* @param {String} id - the ID of the configuration
* @return the configuration or {} if no such configuration is found
*/
readFromLocalStorage: function (id) {
try {
const configuration = localStorage.getItem(id);
if (configuration) {
return JSON.parse(configuration);
}
}
catch (e) {
// ignore any errors
}
return {};
},
/**
* Reads the trend configuration from the local storage and merges it with the default configuration.
*
* @param {String} id - the ID of the configuration
* @return the configuration or {} if no such configuration is found
*/
readConfiguration: function (id) {
const specific = echartsJenkinsApi.readFromLocalStorage(id);
const common = echartsJenkinsApi.readFromLocalStorage(trendDefaultStorageId);
return Object.assign(specific, common);
},
/**
* Fixes the emphasis of the series elements in the specified model.
*
* @param {String} model - the model that contains the chart series
*/
fixEmphasis: function fixEmphasis(model) {
const inheritColors = {
focus: 'series',
color: 'inherit',
areaStyle: {color: 'inherit'}
};
model.series.forEach(seriesElement => {
if (!seriesElement.hasOwnProperty('emphasis') || seriesElement.emphasis === null) {
seriesElement.emphasis = inheritColors; // NOPMD
}
});
},
/**
* Configures the content of the trend configuration dialog.
*
* @param {String} suffix - the suffix for the ID of the affected trend configuration dialog
* @param {Function} fillDialog - a function to fill the configuration dialog with additional values from the JSON
* configuration object
* @param {Function} saveDialog - a function to save the configuration dialog values to the JSON configuration
* object
*/
configureTrend: function (suffix, fillDialog, saveDialog) {
const trendConfiguration = jQuery3('#trend-configuration-' + suffix);
const numberOfBuildsInput = trendConfiguration.find('#builds-' + suffix);
const numberOfDaysInput = trendConfiguration.find('#days-' + suffix);
const useBuildAsDomainCheckBox = trendConfiguration.find('#build-domain-' + suffix);
const useDateAsDomainCheckBox = trendConfiguration.find('#date-domain-' + suffix);
const zeroBasedYAxisCheckBox = trendConfiguration.find('#zero-based-y-axis-' + suffix);
const widthSlider = trendConfiguration.find('#width-' + suffix);
const heightSlider = trendConfiguration.find('#height-' + suffix);
const trendLocalStorageId = 'jenkins-echarts-trend-configuration-' + suffix;
const saveButton = '#save-trend-configuration-' + suffix;
function setDefaultValues() {
numberOfBuildsInput.val(50);
numberOfDaysInput.val(0);
useBuildAsDomainCheckBox.prop('checked', true);
useDateAsDomainCheckBox.prop('checked', false);
widthSlider.val(500);
heightSlider.val(200);
if (fillDialog) {
fillDialog(trendConfiguration, {});
}
}
trendConfiguration.on('show.bs.modal', function (e) {
const trendJsonConfiguration = echartsJenkinsApi.readConfiguration(trendLocalStorageId);
if (jQuery3.isEmptyObject(trendJsonConfiguration)) {
setDefaultValues();
}
else {
try {
numberOfBuildsInput.val(trendJsonConfiguration.numberOfBuilds);
numberOfDaysInput.val(trendJsonConfiguration.numberOfDays);
useBuildAsDomainCheckBox.prop('checked', trendJsonConfiguration.buildAsDomain === 'true');
useDateAsDomainCheckBox.prop('checked', trendJsonConfiguration.buildAsDomain !== 'true');
zeroBasedYAxisCheckBox.prop('checked', trendJsonConfiguration.zeroBasedYAxis === 'true');
widthSlider.val(trendJsonConfiguration.width);
widthSlider.next().html(trendJsonConfiguration.width)
heightSlider.val(trendJsonConfiguration.height);
heightSlider.next().html(trendJsonConfiguration.height)
if (fillDialog) {
fillDialog(trendConfiguration, trendJsonConfiguration);
}
}
catch (e) {
setDefaultValues();
}
}
});
jQuery3(saveButton).on('click', function (e) {
const configurationJson = {
numberOfBuilds: numberOfBuildsInput.val(),
numberOfDays: numberOfDaysInput.val(),
buildAsDomain: useBuildAsDomainCheckBox.prop('checked') ? 'true' : 'false',
zeroBasedYAxis: zeroBasedYAxisCheckBox.prop('checked') ? 'true' : 'false',
width: widthSlider.val(),
height: heightSlider.val()
};
localStorage.setItem(trendDefaultStorageId, JSON.stringify(configurationJson));
if (saveDialog) {
const specific = saveDialog(trendConfiguration);
localStorage.setItem(trendLocalStorageId, JSON.stringify(specific));
}
});
trendConfiguration.on('keypress', function (e) {
if (e.which === 13) {
jQuery3(saveButton).click();
}
});
const slider = jQuery3('.range-slider');
const range = jQuery3('.range-slider-range');
const value = jQuery3('.range-slider-value');
slider.each(function() {
value.each(function() {
const value = jQuery3(this).prev().attr('value');
jQuery3(this).html(value);
});
range.on('input', function() {
jQuery3(this).next(value).html(this.value);
});
});
},
/**
* Configures the content of the trend configuration dialog.
*
* @param {String} suffix - the suffix for the ID of the affected trend configuration dialog
* configuration object
* @param {Function} fillDialog - a function to fill the configuration dialog with additional values from the JSON
* configuration object
* @param {Function} saveDialog - a function to save the configuration dialog values to the JSON configuration
* object
*
*/
configureChart: function (suffix, fillDialog, saveDialog) {
const chartConfiguration = jQuery3('#chart-configuration-' + suffix);
const numberOfBuildsInput = chartConfiguration.find('#builds-' + suffix);
const numberOfDaysInput = chartConfiguration.find('#days-' + suffix);
const useBuildAsDomainCheckBox = chartConfiguration.find('#build-domain-' + suffix);
const trendLocalStorageId = 'jenkins-echarts-chart-configuration-' + suffix;
const saveButton = '#save-chart-configuration-' + suffix;
function setDefaultValues() {
numberOfBuildsInput.val(50);
numberOfDaysInput.val(0);
useBuildAsDomainCheckBox.prop('checked', true);
if (fillDialog) {
fillDialog(chartConfiguration, {});
}
}
chartConfiguration.on('show.bs.modal', function (e) {
const trendJsonConfiguration = echartsJenkinsApi.readFromLocalStorage(trendLocalStorageId);
if (jQuery3.isEmptyObject(trendJsonConfiguration)) {
setDefaultValues();
}
else {
try {
numberOfBuildsInput.val(trendJsonConfiguration.numberOfBuilds);
numberOfDaysInput.val(trendJsonConfiguration.numberOfDays);
useBuildAsDomainCheckBox.prop('checked', trendJsonConfiguration.buildAsDomain === 'true');
if (fillDialog) {
fillDialog(chartConfiguration, trendJsonConfiguration);
}
}
catch (e) {
setDefaultValues();
}
}
});
jQuery3(saveButton).on('click', function (e) {
const configurationJson = {
numberOfBuilds: numberOfBuildsInput.val(),
numberOfDays: numberOfDaysInput.val(),
buildAsDomain: useBuildAsDomainCheckBox.prop('checked') ? 'true' : 'false',
};
if (saveDialog) {
const specific = saveDialog(chartConfiguration);
localStorage.setItem('jenkins-echarts-chart-configuration-' + suffix,
JSON.stringify({... configurationJson, ... specific}));
}
else {
localStorage.setItem('jenkins-echarts-chart-configuration-' + suffix, JSON.stringify(configurationJson));
}
});
chartConfiguration.on('keypress', function (e) {
if (e.which === 13) {
jQuery3(saveButton).click();
}
});
},
/**
* Renders a configurable trend chart in the specified div using ECharts.
*
* @param {String} chartDivId - the ID of the div where the chart should be shown in
* @param {String} model - the line chart model
* @param {String} settingsDialogId - the optional ID of the div that provides a settings dialog (might be set to
* null if there is no such dialog)
* @param {Function} chartClickedEventHandler - the optional event handler that receives click events
* @param {Boolean} allowYAxisZoom - Allow zooming on the y-axis
*/
renderConfigurableZoomableTrendChart: function (chartDivId, model, settingsDialogId, chartClickedEventHandler, allowYAxisZoom = false) {
const chartModel = JSON.parse(echartsJenkinsApi.resolveJenkinsColors(model)); // NOPMD
chartModel.zeroBasedYAxis = chartModel.zeroBasedYAxis ?? false;
const chartPlaceHolder = document.getElementById(chartDivId);
const chart = echarts.init(chartPlaceHolder);
chartPlaceHolder.echart = chart; // NOPMD
const textColor = getComputedStyle(document.body).getPropertyValue('--text-color') || '#333';
const showSettings = document.getElementById(settingsDialogId);
function getDataZoomOptions(allowYAxisZoom) {
const dataZoomOptions = [
{
type: 'inside'
},
{
type: 'slider',
height: 25,
bottom: 5,
moveHandleSize: 5,
xAxisIndex: [0],
filterMode: 'filter'
}
];
if (allowYAxisZoom) {
dataZoomOptions.push({
type: 'slider',
width: 25,
right: 5,
moveHandleSize: 5,
yAxisIndex: [0],
filterMode: 'empty'
});
}
return dataZoomOptions;
}
echartsJenkinsApi.fixEmphasis(chartModel);
const options = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
toolbox: {
show: showSettings != null,
itemSize: 16,
feature: {
myTool1: {
title: 'Setup',
icon: 'ipath://M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z',
onclick: function () {
new bootstrap5.Modal(showSettings).show();
}
}
}
},
dataZoom: getDataZoomOptions(allowYAxisZoom),
legend: {
orient: 'horizontal',
type: 'scroll',
x: 'center',
y: 'top',
textStyle: {
color: textColor
}
},
grid: {
left: '20',
right: allowYAxisZoom ? '40' : '10',
bottom: '35',
top: '40',
containLabel: true
},
xAxis: [{
type: 'category',
boundaryGap: false,
data: chartModel.domainAxisLabels,
axisLabel: {
color: textColor,
showMaxLabel: true
}
}],
yAxis: [{
type: 'value',
min: chartModel.zeroBasedYAxis ? 0 : (chartModel.rangeMin ?? 'dataMin'),
max: chartModel.rangeMax ?? 'dataMax',
axisLabel: {
},
minInterval: chartModel.integerRangeAxis ? 1 : null
}],
series: chartModel.series
};
chart.setOption(options, true);
chart.resize();
if (chartClickedEventHandler !== null) {
chart.getZr().on('click', params => {
const offset = 30;
if (params.offsetY > offset && chart.getHeight() - params.offsetY > offset) { // skip the legend and data zoom
const pointInPixel = [params.offsetX, params.offsetY];
const pointInGrid = chart.convertFromPixel('grid', pointInPixel);
const buildDisplayName = chart.getModel().get('xAxis')[0].data[pointInGrid[0]]
chartClickedEventHandler(buildDisplayName);
}
})
}
jQuery3(window).resize(function () {
chart.resize();
});
},
/**
* Renders a trend chart in the specified div using ECharts.
*
* @param {String} chartDivId - the ID of the div where the chart should be shown in
* @param {String} enableLinks - determines if the chart is clickable. If the chart is clickable, then clicking on a
* chart will open the results of the selected build.
* @param {String} configurationId - ID of the div-element that renders a configuration dialog of this trend chart.
* If this element is defined, then the trend chart will use a configuration button that
* will invoke the specified element. If your trend has no special configuration dialog
* then the ID "defaultTrendConfiguration" of the default configuration dialog should be used.
* @param {Object} ajaxProxy - AJAX proxy of the endpoint in Jenkins Java model object
*/
renderConfigurableTrendChart: function (chartDivId, enableLinks, configurationId, ajaxProxy) {
function hasConfigurationDialog() {
return configurationId != null && configurationId.length > 0;
}
function getConfigurationDialog() {
return document.getElementById('trend-configuration-' + configurationId);
}
function createOptions(chartModel) {
echartsJenkinsApi.fixEmphasis(chartModel);
const textColor = getComputedStyle(document.body).getPropertyValue('--text-color') || '#333';
return {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
},
},
toolbox: {
itemSize: 16,
show: hasConfigurationDialog(),
feature: {
myTool1: {
title: 'Setup',
icon: 'ipath://M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z',
onclick: function () {
new bootstrap5.Modal(getConfigurationDialog()).show();
}
}
}
},
legend: {
orient: 'horizontal',
type: 'scroll',
pageButtonPosition: 'start',
x: 'center',
y: 'top',
textStyle: {
color: textColor
}
},
grid: {
left: '20',
right: '10',
bottom: '10',
top: '30',
containLabel: true
},
xAxis: [{
type: 'category',
boundaryGap: false,
data: chartModel.domainAxisLabels,
axisLabel: {
color: textColor,
showMaxLabel: true
}
}
],
yAxis: [{
type: 'value',
min: chartModel.zeroBasedYAxis ? 0 : (chartModel.rangeMin ?? 'dataMin'),
max: chartModel.rangeMax ?? 'dataMax',
axisLabel: {
color: textColor
},
minInterval: chartModel.integerRangeAxis ? 1 : null
}
],
series: chartModel.series
};
}
/**
* Redraws a trend chart in the specified div using ECharts.
*
* @param {Object} chart - the ECharts instance
* @param {String} model - the line chart model received from the Ajax call
*/
function redraw(chart, model) {
chart.hideLoading();
const themedModel = echartsJenkinsApi.resolveJenkinsColors(model);
const chartModel = JSON.parse(themedModel);
if (hasConfigurationDialog()) {
const trendConfig = echartsJenkinsApi.readConfiguration(
'jenkins-echarts-trend-configuration-' + configurationId
);
chartModel.zeroBasedYAxis = trendConfig.zeroBasedYAxis === 'true'; // NOPMD
}
chart.setOption(createOptions(chartModel), true);
chart.resize();
if (!!(enableLinks && enableLinks !== "false")) {
chartPlaceHolder.model = chartModel;
const urlName = chartPlaceHolder.getAttribute("tool");
if (urlName) {
chart.getZr().on('click', params => {
if (params.offsetY > 30) { // skip the legend
const pointInPixel = [params.offsetX, params.offsetY];
const pointInGrid = chart.convertFromPixel('grid', pointInPixel);
const buildDisplayName = chart.getModel().get('xAxis')[0].data[pointInGrid[0]]
const builds = chartPlaceHolder.model.buildNumbers;
const labels = chartPlaceHolder.model.domainAxisLabels;
let selectedBuild = 0;
for (let i = 0; i < builds.length; i++) {
if (buildDisplayName === labels[i]) {
selectedBuild = builds[i];
break;
}
}
if (selectedBuild > 0) {
const buildUrl = selectedBuild + '/' + urlName;
const evt = params.event;
if (evt && (evt.ctrlKey || evt.metaKey)) {
window.open(buildUrl, '_blank');
} else if (evt && evt.shiftKey) {
window.open(buildUrl, '_newWindow');
} else {
window.location.assign(buildUrl)
}
}
}
})
}
}
}
function renderAsynchronously(chart) {
const configuration = echartsJenkinsApi.readConfiguration('jenkins-echarts-trend-configuration-' + configurationId);
ajaxProxy.getConfigurableBuildTrendModel(JSON.stringify(configuration), function (trendModel) {
redraw(chart, trendModel.responseJSON);
});
}
function setHeight(trend, configuration) {
const height = configuration.height + "px";
trend.style.height = height;
trend.style.minHeight = height;
}
function setWidth(trend, configuration) {
const width = configuration.width + "px";
trend.style.width = width;
trend.style.minWidth = width;
}
function setSize(trend, localStorageId) {
const configuration = echartsJenkinsApi.readConfiguration(localStorageId);
if (configuration && configuration.height) {
setHeight(trend, configuration);
}
if (configuration && configuration.width) {
setWidth(trend, configuration);
}
}
const chartPlaceHolder = document.getElementById(chartDivId);
const chart = echarts.init(chartPlaceHolder);
chart.showLoading();
chartPlaceHolder.echart = chart;
window.onresize = function () {
chart.resize();
};
if (hasConfigurationDialog()) {
const localStorageId = 'jenkins-echarts-trend-configuration-' + configurationId;
setSize(chartPlaceHolder, localStorageId);
renderAsynchronously(chart);
const redrawChartEvent = "echarts.trend.changed";
document.addEventListener(redrawChartEvent, function () {
renderAsynchronously(chart);
});
if (window.getThemeManagerProperty && window.isSystemRespectingTheme) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
renderAsynchronously(chart);
});
}
getConfigurationDialog().addEventListener("hidden.bs.modal", function () {
const configuration = echartsJenkinsApi.readConfiguration(localStorageId);
const trends = document.getElementsByClassName("echarts-trend");
for (let i = 0; i < trends.length; i++) {
setSize(trends[i], configuration);
}
const event = new Event(redrawChartEvent);
document.dispatchEvent(event);
});
}
else { // AsyncTrendChart
ajaxProxy.getBuildTrendModel(function (trendModel) {
redraw(chart, trendModel.responseJSON);
});
}
},
/**
* Renders a trend chart in the specified div using ECharts.
*
* @param {String} chartDivId - the ID of the div where the chart should be shown in
* @param {String} enableLinks - determines if the chart is clickable. If the chart is clickable, then clicking on a
* chart will open the results of the selected build.
* @param {Object} ajaxProxy - AJAX proxy of the endpoint in Jenkins Java model object
*/
renderTrendChart: function (chartDivId, enableLinks, ajaxProxy) {
echartsJenkinsApi.renderConfigurableTrendChart(chartDivId, enableLinks, null, ajaxProxy);
},
/**
* Renders all div elements that have the class 'echarts-pie-chart' using ECharts.
*/
renderPieCharts: function () {
/**
* Renders a trend chart in a div using ECharts.
*
* @param {String} chartDivId - the ID of the div where the chart should be shown in
*/
function renderPieChart(chartDivId) {
function isEmpty(string) {
return (!string || string.length === 0);
}
/**
* Returns the title properties of the chart.
*
* @param {String} title - the title
*/
function getTitle(title) {
if (!isEmpty(title)) {
return {
text: title,
textStyle: {
fontWeight: 'normal',
fontSize: '16'
},
left: 'center'
};
}
return null;
}
const chartPlaceHolder = jQuery3("#" + echartsJenkinsApi.escapeMetaCharacters(chartDivId));
const model = JSON.parse(chartPlaceHolder.attr('data-chart-model'));
const themedColors = model.colors.map(color => color.replaceAll(/--([a-z-]+)/g, function (match) {
return echartsJenkinsApi.resolveJenkinsColor(match)
}));
const title = chartPlaceHolder.attr('data-title');
const chartDiv = chartPlaceHolder[0];
const chart = echarts.init(chartDiv);
chartDiv.echart = chart;
const textColor = echartsJenkinsApi.getTextColor();
const options = {
title: getTitle(title),
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'horizontal',
x: 'center',
y: 'bottom',
type: 'scroll',
textStyle: {
color: textColor
}
},
series: [{
type: 'pie',
radius: ['30%', '70%'],
avoidLabelOverlap: false,
color: themedColors,
label: {
normal: {
show: false,
position: 'center'
},
emphasis: {
show: false
}
},
emphasis: {
itemStyle: {
color: 'inherit'
}
},
labelLine: {
normal: {
show: true
}
},
data: model.data
}
]
};
chart.setOption(options, true);
chart.resize();
const useLinks = chartPlaceHolder.attr('data-links');
if (useLinks && useLinks !== "false") {
chart.on('click', function (params) {
window.location.assign(params.name);
});
}
return chart;
}
const allPieCharts = jQuery3('div.echarts-pie-chart');
const pieChartInstances = [];
allPieCharts.each(function () {
const chart = jQuery3(this);
const id = chart.attr('id');
pieChartInstances.push(renderPieChart(id));
});
if (pieChartInstances.length > 0) {
jQuery3(window).resize(function () {
pieChartInstances.forEach(function (chartInstance) {
chartInstance.resize();
});
});
}
}
}

Xet Storage Details

Size:
31.2 kB
·
Xet hash:
5f63b917b7e14c31d151db9942b5db4ad8412306ee3cece5e8379d855a699e09

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.