You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

200 lines
6.2 KiB

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