gamtest / resources /js /dashboard /chartUtils.js
veela4's picture
Upload folder using huggingface_hub
70ba896 verified
// Chart Utilities for Dashboard
import { CountUp } from 'countup.js';
/**
* Initialize animated counter for metric cards
*/
export function initCounter(element, endValue, options = {}) {
const defaultOptions = {
duration: 2,
useEasing: true,
useGrouping: true,
separator: ',',
decimal: '.',
prefix: '',
suffix: ''
};
const countUp = new CountUp(element, endValue, {
...defaultOptions,
...options
});
if (!countUp.error) {
countUp.start();
} else {
console.error('CountUp error:', countUp.error);
element.textContent = endValue;
}
return countUp;
}
/**
* Format currency values
*/
export function formatCurrency(value, currency = '฿') {
return `${currency}${value.toLocaleString()}`;
}
/**
* Format percentage values
*/
export function formatPercentage(value, decimals = 2) {
return `${value.toFixed(decimals)}%`;
}
/**
* Get trend indicator HTML
*/
export function getTrendIndicator(percentage, showValue = true) {
const isPositive = percentage >= 0;
const icon = isPositive ? '↗' : '↘';
const colorClass = isPositive ? 'text-emerald-400' : 'text-red-400';
const bgClass = isPositive ? 'bg-emerald-400/10' : 'bg-red-400/10';
const valueText = showValue ? `${Math.abs(percentage).toFixed(1)}%` : '';
return `
<div class="flex items-center space-x-1 ${bgClass} px-2 py-1 rounded-full">
<span class="${colorClass} text-sm font-bold">${icon}</span>
${valueText ? `<span class="${colorClass} text-xs font-semibold">${valueText}</span>` : ''}
</div>
`;
}
/**
* Update chart data with smooth animation
*/
export function updateChartData(chart, newData, newLabels = null) {
if (newLabels) {
chart.data.labels = newLabels;
}
// Update datasets
newData.forEach((dataset, index) => {
if (chart.data.datasets[index]) {
chart.data.datasets[index].data = dataset.data;
if (dataset.label) {
chart.data.datasets[index].label = dataset.label;
}
}
});
chart.update('active');
}
/**
* Resize chart to fit container
*/
export function resizeChart(chart) {
if (chart && chart.canvas) {
chart.resize();
}
}
/**
* Destroy chart safely
*/
export function destroyChart(chart) {
if (chart && typeof chart.destroy === 'function') {
chart.destroy();
}
}
/**
* Create loading skeleton for charts
*/
export function createChartSkeleton(container) {
container.innerHTML = `
<div class="animate-pulse">
<div class="h-4 bg-white/10 rounded mb-4 w-1/3"></div>
<div class="space-y-3">
<div class="h-3 bg-white/10 rounded w-full"></div>
<div class="h-3 bg-white/10 rounded w-5/6"></div>
<div class="h-3 bg-white/10 rounded w-4/6"></div>
<div class="h-3 bg-white/10 rounded w-3/6"></div>
<div class="h-3 bg-white/10 rounded w-2/6"></div>
</div>
</div>
`;
}
/**
* Show error state for charts
*/
export function showChartError(container, message = 'Failed to load chart data') {
container.innerHTML = `
<div class="flex flex-col items-center justify-center h-full text-center p-6">
<div class="w-12 h-12 bg-red-500/20 rounded-full flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<p class="text-white/60 text-sm mb-3">${message}</p>
<button onclick="location.reload()" class="px-4 py-2 bg-white/10 hover:bg-white/20 text-white text-xs rounded-lg transition-colors">
Retry
</button>
</div>
`;
}
/**
* Debounce function for performance
*/
export function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
/**
* Throttle function for performance
*/
export function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
/**
* Format time labels based on period
*/
export function formatTimeLabel(value, period) {
switch (period) {
case 'daily':
return `${value}:00`;
case 'weekly':
return value; // Day name
case 'monthly':
return `Day ${value}`;
default:
return value;
}
}
/**
* Generate random data for demo purposes
*/
export function generateDemoData(length, min = 0, max = 1000) {
return Array.from({ length }, () => Math.floor(Math.random() * (max - min + 1)) + min);
}
/**
* Calculate percentage change
*/
export function calculatePercentageChange(current, previous) {
if (previous === 0) return current > 0 ? 100 : 0;
return ((current - previous) / previous) * 100;
}
/**
* Get responsive chart height based on screen size
*/
export function getResponsiveHeight() {
const width = window.innerWidth;
if (width < 640) return 250; // Mobile
if (width < 1024) return 300; // Tablet
return 350; // Desktop
}
/**
* Initialize chart container with glassmorphic styling
*/
export function initChartContainer(container) {
container.classList.add(
'bg-white/5',
'backdrop-blur-2xl',
'border',
'border-white/10',
'rounded-2xl',
'p-6',
'shadow-2xl'
);
// Add glow effect
container.style.boxShadow = `
0 25px 50px -12px rgba(0, 0, 0, 0.6),
0 0 0 1px rgba(255, 255, 255, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.1)
`;
}
// Make functions globally available for inline scripts
window.createChartSkeleton = createChartSkeleton;
window.showChartError = showChartError;
window.initChartContainer = initChartContainer;
window.formatCurrency = formatCurrency;
window.formatPercentage = formatPercentage;
window.getTrendIndicator = getTrendIndicator;
window.calculatePercentageChange = calculatePercentageChange;
window.updateChartData = updateChartData;
window.resizeChart = resizeChart;
window.destroyChart = destroyChart;