|
|
|
|
|
let userChart, revenueChart, performanceChart, activityChart; |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
setupCharts(); |
|
|
startDataUpdates(); |
|
|
}); |
|
|
|
|
|
function setupCharts() { |
|
|
const chartOptions = { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
plugins: { |
|
|
legend: { |
|
|
position: 'top', |
|
|
labels: { |
|
|
color: window.matchMedia('(prefers-color-scheme: dark)').matches ? '#fff' : '#000' |
|
|
} |
|
|
} |
|
|
}, |
|
|
scales: { |
|
|
y: { |
|
|
beginAtZero: true, |
|
|
grid: { |
|
|
color: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)' |
|
|
}, |
|
|
ticks: { |
|
|
color: window.matchMedia('(prefers-color-scheme: dark)').matches ? '#fff' : '#000' |
|
|
} |
|
|
}, |
|
|
x: { |
|
|
grid: { |
|
|
color: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)' |
|
|
}, |
|
|
ticks: { |
|
|
color: window.matchMedia('(prefers-color-scheme: dark)').matches ? '#fff' : '#000' |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const userCtx = document.getElementById('userChart').getContext('2d'); |
|
|
userChart = new Chart(userCtx, { |
|
|
type: 'line', |
|
|
data: { |
|
|
labels: Array.from({length: 12}, (_, i) => `${i+1}AM`), |
|
|
datasets: [{ |
|
|
label: 'Active Users', |
|
|
data: Array(12).fill(0).map((_,i) => { |
|
|
const hourFactor = Math.sin((i / 11) * Math.PI) * 0.5 + 0.7; |
|
|
return Math.floor(3000 + (hourFactor * 2000) + Math.random() * 500 - 250); |
|
|
}), |
|
|
borderColor: '#6366f1', |
|
|
backgroundColor: 'rgba(99, 102, 241, 0.1)', |
|
|
tension: 0.4, |
|
|
fill: true |
|
|
}] |
|
|
}, |
|
|
options: chartOptions |
|
|
}); |
|
|
|
|
|
|
|
|
const revenueCtx = document.getElementById('revenueChart').getContext('2d'); |
|
|
revenueChart = new Chart(revenueCtx, { |
|
|
type: 'doughnut', |
|
|
data: { |
|
|
labels: ['Subscriptions', 'Ads', 'Merchandise', 'Services'], |
|
|
datasets: [{ |
|
|
data: [35, 25, 20, 20].map(v => v + Math.floor(Math.random() * 10 - 5)), |
|
|
backgroundColor: [ |
|
|
'#6366f1', |
|
|
'#8b5cf6', |
|
|
'#ec4899', |
|
|
'#f43f5e' |
|
|
], |
|
|
borderWidth: 0 |
|
|
}] |
|
|
}, |
|
|
options: { |
|
|
...chartOptions, |
|
|
cutout: '70%' |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const performanceCtx = document.getElementById('performanceChart').getContext('2d'); |
|
|
performanceChart = new Chart(performanceCtx, { |
|
|
type: 'bar', |
|
|
data: { |
|
|
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], |
|
|
datasets: [ |
|
|
{ |
|
|
label: 'Page Load (ms)', |
|
|
data: Array(7).fill(0).map(() => 100 + Math.floor(Math.random() * 300)), |
|
|
backgroundColor: '#6366f1' |
|
|
}, |
|
|
{ |
|
|
label: 'API Response (ms)', |
|
|
data: Array(7).fill(0).map(() => 50 + Math.floor(Math.random() * 150)), |
|
|
backgroundColor: '#8b5cf6' |
|
|
} |
|
|
] |
|
|
}, |
|
|
options: chartOptions |
|
|
}); |
|
|
|
|
|
|
|
|
const activityCtx = document.getElementById('activityChart').getContext('2d'); |
|
|
activityChart = new Chart(activityCtx, { |
|
|
type: 'line', |
|
|
data: { |
|
|
labels: Array.from({length: 24}, (_, i) => i), |
|
|
datasets: [ |
|
|
{ |
|
|
label: 'API Calls', |
|
|
data: Array(24).fill(0).map((_, i) => { |
|
|
const isDaytime = i >= 9 && i < 18; |
|
|
const base = isDaytime ? 800 : 300; |
|
|
const hourFactor = Math.sin((i / 24) * Math.PI * 1.5) * (isDaytime ? 400 : 150); |
|
|
const noise = Math.random() * 150 - 75; |
|
|
return Math.max(100, base + hourFactor + noise); |
|
|
}), |
|
|
borderColor: '#6366f1', |
|
|
backgroundColor: 'rgba(99, 102, 241, 0.1)', |
|
|
tension: 0.3, |
|
|
fill: true |
|
|
} |
|
|
] |
|
|
}, |
|
|
options: chartOptions |
|
|
}); |
|
|
} |
|
|
function startDataUpdates() { |
|
|
|
|
|
setInterval(() => { |
|
|
const cards = document.querySelectorAll('metrics-card'); |
|
|
const now = new Date(); |
|
|
const hour = now.getHours(); |
|
|
|
|
|
|
|
|
const userBase = hour < 6 ? 2000 : hour < 12 ? 4000 : hour < 18 ? 6000 : 3000; |
|
|
const users = userBase + Math.floor(Math.random() * 500 - 250); |
|
|
const userChange = (Math.random() > 0.6 ? 1 : -1) * (0.5 + Math.random() * 2); |
|
|
cards[0].setAttribute('value', users.toLocaleString()); |
|
|
cards[0].setAttribute('change', userChange.toFixed(1)); |
|
|
|
|
|
|
|
|
const conversionBase = hour >= 9 && hour < 17 ? 3.5 : 1.8; |
|
|
const conversion = (conversionBase + Math.random() * 0.8 - 0.4).toFixed(2); |
|
|
const conversionChange = (Math.random() * 0.4 - 0.2).toFixed(2); |
|
|
cards[1].setAttribute('value', conversion + '%'); |
|
|
cards[1].setAttribute('change', conversionChange); |
|
|
|
|
|
|
|
|
const latencyBase = hour >= 10 && hour < 16 ? 80 : 40; |
|
|
const latency = Math.floor(latencyBase + Math.random() * 30); |
|
|
const latencyChange = (Math.random() > 0.3 ? 1 : -1) * (1 + Math.random() * 4); |
|
|
cards[2].setAttribute('value', latency + 'ms'); |
|
|
cards[2].setAttribute('change', latencyChange.toFixed(1)); |
|
|
|
|
|
|
|
|
const revenueBase = hour < 6 ? 8000 : hour < 12 ? 12000 : hour < 18 ? 15000 : 9000; |
|
|
const revenue = revenueBase + Math.floor(Math.random() * 3000 - 1500); |
|
|
const revenueChange = (Math.random() > 0.5 ? 1 : -1) * (2 + Math.random() * 5); |
|
|
cards[3].setAttribute('value', '// Update charts with new data points |
|
|
updateCharts(); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
function updateCharts() { |
|
|
// User Chart - follows daily pattern with realistic variations |
|
|
const userData = userChart.data.datasets[0].data; |
|
|
userData.shift(); |
|
|
const userTrend = hour < 6 ? 0.8 : hour < 12 ? 1.2 : hour < 18 ? 1.5 : 0.9; |
|
|
userData.push(Math.max(0, userData[userData.length-1] * (0.98 + Math.random() * 0.04) * userTrend)); |
|
|
userChart.update(); |
|
|
|
|
|
// Revenue Chart - more stable proportions with slight variations |
|
|
revenueChart.data.datasets[0].data = [30, 25, 25, 20].map(v => v * (0.95 + Math.random() * 0.1)); |
|
|
revenueChart.update(); |
|
|
|
|
|
// Performance Chart - weekday patterns with better/worse performance |
|
|
const weekday = now.getDay(); |
|
|
const isWeekday = weekday > 0 && weekday < 6; |
|
|
performanceChart.data.datasets[0].data = Array(7).fill(0).map((_,i) => |
|
|
isWeekday ? 80 + Math.floor(Math.random() * 120) : 60 + Math.floor(Math.random() * 80)); |
|
|
performanceChart.data.datasets[1].data = Array(7).fill(0).map((_,i) => |
|
|
isWeekday ? 40 + Math.floor(Math.random() * 70) : 30 + Math.floor(Math.random() * 40)); |
|
|
performanceChart.update(); |
|
|
|
|
|
// Activity Chart - more realistic API call patterns |
|
|
const activityData = activityChart.data.datasets[0].data; |
|
|
activityData.shift(); |
|
|
const activityTrend = hour < 6 ? 0.7 : hour < 9 ? 0.9 : hour < 17 ? 1.3 : 1.0; |
|
|
const newActivity = Math.max(100, activityData[activityData.length-1] * (0.97 + Math.random() * 0.06) * activityTrend); |
|
|
activityData.push(newActivity); |
|
|
activityChart.update(); |
|
|
} |
|
|
|
|
|
// Handle dark mode changes |
|
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { |
|
|
document.documentElement.classList.toggle('dark', e.matches); |
|
|
setupCharts(); |
|
|
}); + revenue.toLocaleString()); |
|
|
cards[3].setAttribute('change', revenueChange.toFixed(1)); |
|
|
// Update charts with new data points |
|
|
updateCharts(); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
function updateCharts() { |
|
|
// User Chart - add new point and remove oldest |
|
|
const userData = userChart.data.datasets[0].data; |
|
|
userData.shift(); |
|
|
userData.push(Math.floor(Math.random() * 5000)); |
|
|
userChart.update(); |
|
|
|
|
|
// Revenue Chart - update values with slight variations |
|
|
revenueChart.data.datasets[0].data = [35, 25, 20, 20].map(v => v + Math.floor(Math.random() * 10 - 5)); |
|
|
revenueChart.update(); |
|
|
|
|
|
// Performance Chart - update with new random values |
|
|
performanceChart.data.datasets[0].data = Array(7).fill(0).map(() => 100 + Math.floor(Math.random() * 300)); |
|
|
performanceChart.data.datasets[1].data = Array(7).fill(0).map(() => 50 + Math.floor(Math.random() * 150)); |
|
|
performanceChart.update(); |
|
|
|
|
|
// Activity Chart - shift data and add new point |
|
|
const activityData = activityChart.data.datasets[0].data; |
|
|
activityData.shift(); |
|
|
const lastValue = activityData[activityData.length - 1]; |
|
|
activityData.push(Math.max(0, lastValue + (Math.random() * 100 - 50))); |
|
|
activityChart.update(); |
|
|
} |
|
|
|
|
|
// Handle dark mode changes |
|
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { |
|
|
document.documentElement.classList.toggle('dark', e.matches); |
|
|
setupCharts(); |
|
|
}); |