/** * Chart utilities for activity visualization */ class ActivityChart { constructor(canvasId) { this.canvasId = canvasId; this.chart = null; this.initChart(); } initChart() { // Check if Chart.js is available if (typeof Chart === "undefined") { console.error("Chart.js is not loaded"); return; } const chartElement = document.getElementById(this.canvasId); if (!chartElement) { console.error( `Chart canvas element with id "${this.canvasId}" not found` ); return; } this.ctx = chartElement.getContext("2d"); } /** * Render the activity chart with provided data * @param {Array} data - Chart data array */ render(data) { if (!this.ctx) { console.error("Chart context not available"); return; } // Extract the data for the chart const labels = data.map((item) => `${item.hour}:00`); const successData = data.map((item) => item.success); const errorData = data.map((item) => item.error); const pendingData = data.map((item) => item.pending); // Destroy existing chart if it exists if (this.chart) { this.chart.destroy(); } this.chart = new Chart(this.ctx, { type: "bar", data: { labels: labels, datasets: [ { label: "Success", data: successData, backgroundColor: "#28a745", stack: "Stack 0", }, { label: "Error", data: errorData, backgroundColor: "#dc3545", stack: "Stack 0", }, { label: "Pending", data: pendingData, backgroundColor: "#ffc107", stack: "Stack 0", }, ], }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { stacked: true, title: { display: true, text: "Hour", }, }, y: { stacked: true, beginAtZero: true, title: { display: true, text: "Papers Scraped", }, }, }, }, }); } /** * Load and render chart data for specified time range * @param {number} hours - Number of hours to show data for */ async loadData(hours) { try { const response = await fetch(`/scraper/stats?hours=${hours}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log("Stats data loaded:", data); this.render(data); } catch (error) { console.error("Failed to load activity stats:", error); // Hide the chart or show an error message const chartContainer = document.getElementById( this.canvasId ).parentElement; if (chartContainer) { chartContainer.innerHTML = '

Chart data unavailable

'; } } } /** * Destroy the chart instance */ destroy() { if (this.chart) { this.chart.destroy(); this.chart = null; } } }