Improve: Storage donut chart with native SVG rendering - remove dependency on Victory.js
This commit is contained in:
@@ -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>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user