import Theme from '../../utils/theme.js';
|
|
|
|
export default (function () {
|
|
|
|
// Vanilla JS Pie Chart implementation using SVG
|
|
class VanillaPieChart {
|
|
constructor(element, options = {}) {
|
|
this.element = element;
|
|
this.options = {
|
|
size: 110,
|
|
lineWidth: 3,
|
|
lineCap: 'round',
|
|
trackColor: '#f2f2f2',
|
|
barColor: '#ef1e25',
|
|
scaleColor: false,
|
|
animate: 1000,
|
|
onStep: null,
|
|
...options,
|
|
};
|
|
|
|
this.percentage = parseInt(element.dataset.percent || 0);
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.createSVG();
|
|
this.animate();
|
|
}
|
|
|
|
createSVG() {
|
|
const size = this.options.size;
|
|
const lineWidth = this.options.lineWidth;
|
|
const radius = (size - lineWidth) / 2;
|
|
const circumference = 2 * Math.PI * radius;
|
|
|
|
// Create SVG element
|
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
svg.setAttribute('width', size);
|
|
svg.setAttribute('height', size);
|
|
svg.style.transform = 'rotate(-90deg)';
|
|
|
|
// Create track (background circle)
|
|
const track = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
track.setAttribute('cx', size / 2);
|
|
track.setAttribute('cy', size / 2);
|
|
track.setAttribute('r', radius);
|
|
track.setAttribute('fill', 'none');
|
|
track.setAttribute('stroke', this.options.trackColor);
|
|
track.setAttribute('stroke-width', lineWidth);
|
|
|
|
// Create bar (progress circle)
|
|
const bar = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
bar.setAttribute('cx', size / 2);
|
|
bar.setAttribute('cy', size / 2);
|
|
bar.setAttribute('r', radius);
|
|
bar.setAttribute('fill', 'none');
|
|
bar.setAttribute('stroke', this.options.barColor);
|
|
bar.setAttribute('stroke-width', lineWidth);
|
|
bar.setAttribute('stroke-linecap', this.options.lineCap);
|
|
bar.setAttribute('stroke-dasharray', circumference);
|
|
bar.setAttribute('stroke-dashoffset', circumference);
|
|
|
|
// Add elements to SVG
|
|
svg.appendChild(track);
|
|
svg.appendChild(bar);
|
|
|
|
// Clear element and add SVG
|
|
this.element.innerHTML = '';
|
|
this.element.appendChild(svg);
|
|
|
|
// Add percentage text
|
|
const textElement = document.createElement('div');
|
|
textElement.style.position = 'absolute';
|
|
textElement.style.top = '50%';
|
|
textElement.style.left = '50%';
|
|
textElement.style.transform = 'translate(-50%, -50%)';
|
|
textElement.style.fontSize = '14px';
|
|
textElement.style.fontWeight = 'bold';
|
|
textElement.style.color = Theme.getCSSVar('--c-text-base') || '#333';
|
|
textElement.textContent = '0%';
|
|
|
|
this.element.style.position = 'relative';
|
|
this.element.appendChild(textElement);
|
|
|
|
// Store references
|
|
this.svg = svg;
|
|
this.bar = bar;
|
|
this.textElement = textElement;
|
|
this.circumference = circumference;
|
|
}
|
|
|
|
animate() {
|
|
const targetOffset = this.circumference - (this.percentage / 100) * this.circumference;
|
|
const duration = this.options.animate;
|
|
const startTime = Date.now();
|
|
const startOffset = this.circumference;
|
|
|
|
const animateStep = () => {
|
|
const elapsed = Date.now() - startTime;
|
|
const progress = Math.min(elapsed / duration, 1);
|
|
|
|
// Easing function (easeOutCubic)
|
|
const easeProgress = 1 - Math.pow(1 - progress, 3);
|
|
|
|
const currentOffset = startOffset - (startOffset - targetOffset) * easeProgress;
|
|
const currentPercent = ((this.circumference - currentOffset) / this.circumference) * 100;
|
|
|
|
this.bar.setAttribute('stroke-dashoffset', currentOffset);
|
|
this.textElement.textContent = `${Math.round(currentPercent)}%`;
|
|
|
|
// Call onStep callback if provided
|
|
if (this.options.onStep) {
|
|
this.options.onStep.call(this, 0, this.percentage, currentPercent);
|
|
}
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(animateStep);
|
|
}
|
|
};
|
|
|
|
requestAnimationFrame(animateStep);
|
|
}
|
|
|
|
update(percentage) {
|
|
this.percentage = percentage;
|
|
this.animate();
|
|
}
|
|
|
|
destroy() {
|
|
if (this.element) {
|
|
this.element.innerHTML = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize all pie charts
|
|
const initializePieCharts = () => {
|
|
const pieChartElements = document.querySelectorAll('.easy-pie-chart');
|
|
|
|
pieChartElements.forEach(element => {
|
|
// Skip if already initialized
|
|
if (element.pieChartInstance) {
|
|
element.pieChartInstance.destroy();
|
|
}
|
|
|
|
// Get theme colors
|
|
const isDark = Theme.current() === 'dark';
|
|
const barColor = element.dataset.barColor || (isDark ? '#4f46e5' : '#ef4444');
|
|
const trackColor = element.dataset.trackColor || (isDark ? '#374151' : '#f3f4f6');
|
|
|
|
// Create pie chart instance
|
|
const pieChart = new VanillaPieChart(element, {
|
|
size: parseInt(element.dataset.size || 110),
|
|
lineWidth: parseInt(element.dataset.lineWidth || 3),
|
|
barColor,
|
|
trackColor,
|
|
animate: parseInt(element.dataset.animate || 1000),
|
|
onStep(from, to, percent) {
|
|
// Update the percentage display
|
|
const textElement = this.element.querySelector('div');
|
|
if (textElement) {
|
|
textElement.innerHTML = `${Math.round(percent)}%`;
|
|
}
|
|
},
|
|
});
|
|
|
|
// Store instance for cleanup
|
|
element.pieChartInstance = pieChart;
|
|
});
|
|
};
|
|
|
|
// Initialize on load
|
|
initializePieCharts();
|
|
|
|
// Reinitialize on theme change
|
|
window.addEventListener('adminator:themeChanged', () => {
|
|
setTimeout(initializePieCharts, 100);
|
|
});
|
|
|
|
// Reinitialize on window resize
|
|
window.addEventListener('resize', () => {
|
|
setTimeout(initializePieCharts, 100);
|
|
});
|
|
|
|
// Cleanup on page unload
|
|
window.addEventListener('beforeunload', () => {
|
|
const pieChartElements = document.querySelectorAll('.easy-pie-chart');
|
|
pieChartElements.forEach(element => {
|
|
if (element.pieChartInstance) {
|
|
element.pieChartInstance.destroy();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Return public API
|
|
return {
|
|
init: initializePieCharts,
|
|
VanillaPieChart,
|
|
};
|
|
}());
|