Improve: Storage donut chart with native SVG rendering - remove dependency on Victory.js

This commit is contained in:
2025-10-27 13:23:44 -03:00
parent 07f004f2a6
commit 19baa430cc

View File

@@ -2786,86 +2786,88 @@
const utilizationPercentage = Math.min(100, Math.max(0, storageUtilization)); const utilizationPercentage = Math.min(100, Math.max(0, storageUtilization));
const unusedPercentage = 100 - utilizationPercentage; const unusedPercentage = 100 - utilizationPercentage;
// Create donut chart using Victory.js
const container = document.getElementById('storage-donut-chart'); const container = document.getElementById('storage-donut-chart');
// Clear container
container.innerHTML = '';
// Check if Victory is available
if (typeof Victory === 'undefined' || typeof React === 'undefined') {
throw new Error('Victory.js or React not available');
}
// Create donut chart data
const chartData = [
{ x: 'Used', y: utilizationPercentage },
{ x: 'Unused', y: unusedPercentage }
];
// Determine color based on utilization // Determine color based on utilization
const usedColor = utilizationPercentage >= 90 ? '#dc2626' : const usedColor = utilizationPercentage >= 90 ? '#ef4444' :
utilizationPercentage >= 75 ? '#f59e0b' : utilizationPercentage >= 75 ? '#f59e0b' :
utilizationPercentage >= 50 ? '#3b82f6' : '#22c55e'; utilizationPercentage >= 50 ? '#3b82f6' : '#22c55e';
// Create VictoryPie component // Create SVG donut chart
const donutChart = React.createElement(Victory.VictoryPie, { const width = 250;
data: chartData, const height = 250;
width: 300, const radius = Math.min(width, height) / 2 - 10;
height: 300, const innerRadius = radius * 0.6;
innerRadius: 80, const cx = width / 2;
colorScale: [usedColor, '#374151'], const cy = height / 2;
style: {
data: {
stroke: '#1f2937',
strokeWidth: 3
}
},
labels: () => null,
animate: {
duration: 1000,
onLoad: { duration: 500 }
}
});
// Create center text // Calculate angles for the donut
const centerText = React.createElement('text', { const usedAngle = (utilizationPercentage / 100) * 360;
x: 150, const unusedAngle = 360 - usedAngle;
y: 140,
textAnchor: 'middle',
dominantBaseline: 'middle',
style: {
fontSize: '28px',
fontWeight: 'bold',
fill: '#f9fafb'
}
}, `${utilizationPercentage.toFixed(1)}%`);
// Create subtitle // Convert angle to radians and create arc paths
const subtitle = React.createElement('text', { const getPath = (angle) => {
x: 150, const startAngle = -90; // Start from top
y: 170, const endAngle = startAngle + angle;
textAnchor: 'middle', const startAngleRad = (startAngle * Math.PI) / 180;
dominantBaseline: 'middle', const endAngleRad = (endAngle * Math.PI) / 180;
style: {
fontSize: '14px', const x1 = cx + radius * Math.cos(startAngleRad);
fill: '#9ca3af' const y1 = cy + radius * Math.sin(startAngleRad);
} const x2 = cx + radius * Math.cos(endAngleRad);
}, `${formatBytes(totalStorageUsed)} used`); const y2 = cy + radius * Math.sin(endAngleRad);
const x3 = cx + innerRadius * Math.cos(endAngleRad);
const y3 = cy + innerRadius * Math.sin(endAngleRad);
const x4 = cx + innerRadius * Math.cos(startAngleRad);
const y4 = cy + innerRadius * Math.sin(startAngleRad);
const largeArcFlag = angle > 180 ? 1 : 0;
return `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2} L ${x3} ${y3} A ${innerRadius} ${innerRadius} 0 ${largeArcFlag} 0 ${x4} ${y4} Z`;
};
// Create SVG container const usedPath = getPath(usedAngle);
const svgContainer = React.createElement('svg', { const unusedPath = getPath(unusedAngle);
width: 300,
height: 300,
viewBox: '0 0 300 300'
}, donutChart, centerText, subtitle);
// Render the chart container.innerHTML = `
ReactDOM.render(svgContainer, container); <svg width="${width}" height="${height}" style="display: block; margin: 0 auto;">
<!-- Used arc -->
<path d="${usedPath}"
fill="${usedColor}"
stroke="#1f2937"
stroke-width="2">
<animate attributeName="opacity" values="0;1" dur="0.8s" fill="freeze" />
</path>
<!-- Unused arc -->
<path d="${unusedPath}"
fill="#374151"
stroke="#1f2937"
stroke-width="2">
<animate attributeName="opacity" values="0;1" dur="0.8s" fill="freeze" />
</path>
<!-- Center text with percentage -->
<text x="${cx}" y="${cy - 5}"
text-anchor="middle"
dominant-baseline="middle"
style="font-size: 36px; font-weight: bold; fill: #f9fafb; font-family: system-ui;">
${utilizationPercentage.toFixed(1)}%
</text>
<!-- Subtitle -->
<text x="${cx}" y="${cy + 25}"
text-anchor="middle"
dominant-baseline="middle"
style="font-size: 14px; fill: #9ca3af; font-family: system-ui;">
${formatBytes(totalStorageUsed)}
</text>
</svg>
`;
} catch (error) { } catch (error) {
console.error('Error updating storage donut chart:', error); console.error('Error updating storage donut chart:', error);
// Fallback to simple text display
const container = document.getElementById('storage-donut-chart'); const container = document.getElementById('storage-donut-chart');
const storageUtilization = data.storage_utilization_percent || 0; const storageUtilization = data.storage_utilization_percent || 0;
const totalStorageUsed = data.total_storage_used || 0; const totalStorageUsed = data.total_storage_used || 0;
@@ -2881,9 +2883,6 @@
<div style="font-size: 16px; color: var(--pf-global--Color--200);"> <div style="font-size: 16px; color: var(--pf-global--Color--200);">
${formatBytes(totalStorageUsed)} used ${formatBytes(totalStorageUsed)} used
</div> </div>
<div style="font-size: 12px; color: var(--pf-global--Color--400); margin-top: 8px;">
Victory.js not available
</div>
</div> </div>
`; `;
} }