/
varsl
/
HTML-CSS
Обзор
Документация
Войти
/
varsl
/
HTML-CSS
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
frontend/js/chart-config.js
776 строк
25 KB
varsl
Добавление JavaScript логики
21 дек 2025, 23:16
21 дек 2025, 23:16
9dd2130
Код
Авторство
О чём код?
class ChartManager { constructor() { this.charts = new Map(); this.init(); } init() { this.setupChartTemplates(); this.setupChartInteractions(); } setupChartTemplates() { this.templates = { line: { type: 'line', options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top', labels: { padding: 20, font: { size: 12 } } }, tooltip: { mode: 'index', intersect: false, backgroundColor: 'rgba(0, 0, 0, 0.7)', titleFont: { size: 14 }, bodyFont: { size: 13 }, padding: 12 } }, scales: { x: { grid: { display: false, drawBorder: false }, ticks: { font: { size: 11 } } }, y: { beginAtZero: true, grid: { drawBorder: false, color: 'rgba(0, 0, 0, 0.05)' }, ticks: { font: { size: 11 }, padding: 10 } } }, interaction: { intersect: false, mode: 'nearest' }, animation: { duration: 1000, easing: 'easeOutQuart' } } }, bar: { type: 'bar', options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top', labels: { padding: 20, font: { size: 12 }, usePointStyle: true } } }, scales: { x: { grid: { display: false }, ticks: { font: { size: 11 } } }, y: { beginAtZero: true, grid: { drawBorder: false, color: 'rgba(0, 0, 0, 0.05)' }, ticks: { font: { size: 11 }, padding: 10 } } } } }, doughnut: { type: 'doughnut', options: { responsive: true, maintainAspectRatio: false, cutout: '70%', plugins: { legend: { position: 'right', labels: { padding: 20, font: { size: 12 }, usePointStyle: true, boxWidth: 12, boxHeight: 12 } }, tooltip: { callbacks: { label: function(context) { let label = context.label || ''; if (label) { label += ': '; } label += context.parsed + '%'; return label; } } } }, animation: { animateScale: true, animateRotate: true } } }, pie: { type: 'pie', options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { padding: 20, font: { size: 12 }, usePointStyle: true } } } } }, radar: { type: 'radar', options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top', labels: { padding: 20, font: { size: 12 } } } }, scales: { r: { beginAtZero: true, ticks: { display: false }, grid: { color: 'rgba(0, 0, 0, 0.1)' } } } } } }; } createChart(canvasId, config) { const canvas = document.getElementById(canvasId); if (!canvas) { console.error(`Canvas element with id "${canvasId}" not found`); return null; } if (this.charts.has(canvasId)) { this.charts.get(canvasId).destroy(); } const ctx = canvas.getContext('2d'); const template = this.templates[config.type] || this.templates.line; const chartConfig = { type: template.type, data: config.data, options: { ...template.options, ...config.options }, plugins: config.plugins || [] }; const chart = new Chart(ctx, chartConfig); this.charts.set(canvasId, chart); this.setupChartEvents(canvasId, chart); return chart; } setupChartEvents(canvasId, chart) { const canvas = document.getElementById(canvasId); if (!canvas) return; canvas.addEventListener('click', (event) => { const points = chart.getElementsAtEventForMode(event, 'nearest', { intersect: true }, true); if (points.length) { const firstPoint = points[0]; const datasetIndex = firstPoint.datasetIndex; const index = firstPoint.index; this.handleChartClick(canvasId, datasetIndex, index); } }); canvas.addEventListener('mouseenter', () => { canvas.style.cursor = 'pointer'; }); canvas.addEventListener('mouseleave', () => { canvas.style.cursor = 'default'; }); } handleChartClick(canvasId, datasetIndex, index) { const chart = this.charts.get(canvasId); if (!chart) return; const meta = chart.getDatasetMeta(datasetIndex); const data = chart.data.datasets[datasetIndex].data[index]; const label = chart.data.labels[index]; const datasetLabel = chart.data.datasets[datasetIndex].label; console.log(`Chart clicked: ${datasetLabel} - ${label}: ${data}`); this.showChartTooltip(chart, datasetIndex, index); this.animateChartSegment(chart, datasetIndex, index); } showChartTooltip(chart, datasetIndex, index) { const tooltip = chart.tooltip; if (tooltip) { tooltip.setActiveElements([ { datasetIndex, index } ], { x: 0, y: 0 }); chart.update(); setTimeout(() => { tooltip.setActiveElements([], { x: 0, y: 0 }); chart.update(); }, 2000); } } animateChartSegment(chart, datasetIndex, index) { const meta = chart.getDatasetMeta(datasetIndex); const element = meta.data[index]; if (element) { const originalRadius = element.options.radius || 3; const originalBorderWidth = element.options.borderWidth || 1; element.options.radius = originalRadius * 1.5; element.options.borderWidth = originalBorderWidth * 2; chart.update(); setTimeout(() => { element.options.radius = originalRadius; element.options.borderWidth = originalBorderWidth; chart.update(); }, 500); } } updateChartData(canvasId, newData) { const chart = this.charts.get(canvasId); if (!chart) return; chart.data = newData; chart.update(); } updateChartDataset(canvasId, datasetIndex, newDataset) { const chart = this.charts.get(canvasId); if (!chart) return; chart.data.datasets[datasetIndex] = newDataset; chart.update(); } addChartData(canvasId, newLabel, newData) { const chart = this.charts.get(canvasId); if (!chart) return; chart.data.labels.push(newLabel); chart.data.datasets.forEach((dataset, index) => { if (newData[index] !== undefined) { dataset.data.push(newData[index]); } }); chart.update(); } removeChartData(canvasId, index) { const chart = this.charts.get(canvasId); if (!chart) return; chart.data.labels.splice(index, 1); chart.data.datasets.forEach(dataset => { dataset.data.splice(index, 1); }); chart.update(); } changeChartType(canvasId, newType) { const chart = this.charts.get(canvasId); if (!chart) return; chart.config.type = newType; chart.update(); } exportChartAsImage(canvasId, filename = 'chart.png') { const canvas = document.getElementById(canvasId); if (!canvas) return; const link = document.createElement('a'); link.download = filename; link.href = canvas.toDataURL('image/png'); link.click(); } exportChartAsCSV(canvasId, filename = 'chart_data.csv') { const chart = this.charts.get(canvasId); if (!chart) return; const labels = chart.data.labels; const datasets = chart.data.datasets; let csvContent = 'Категория,' + datasets.map(d => d.label).join(',') + '\n'; labels.forEach((label, index) => { const row = [label]; datasets.forEach(dataset => { row.push(dataset.data[index] || ''); }); csvContent += row.join(',') + '\n'; }); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } getChartStats(canvasId) { const chart = this.charts.get(canvasId); if (!chart) return null; const datasets = chart.data.datasets; const stats = []; datasets.forEach(dataset => { const data = dataset.data.filter(d => typeof d === 'number'); const sum = data.reduce((a, b) => a + b, 0); const avg = sum / data.length; const max = Math.max(...data); const min = Math.min(...data); stats.push({ label: dataset.label, sum, avg, max, min, count: data.length }); }); return stats; } setupChartInteractions() { this.setupChartFilters(); this.setupChartExport(); this.setupChartControls(); } setupChartFilters() { const filterButtons = document.querySelectorAll('.chart-filter'); filterButtons.forEach(button => { button.addEventListener('click', (e) => { const canvasId = e.target.getAttribute('data-chart'); const filterType = e.target.getAttribute('data-filter'); this.applyChartFilter(canvasId, filterType); filterButtons.forEach(btn => btn.classList.remove('active')); e.target.classList.add('active'); }); }); } applyChartFilter(canvasId, filterType) { const chart = this.charts.get(canvasId); if (!chart) return; switch(filterType) { case 'monthly': this.showMonthlyData(chart); break; case 'weekly': this.showWeeklyData(chart); break; case 'daily': this.showDailyData(chart); break; case 'all': this.showAllData(chart); break; } } showMonthlyData(chart) { console.log('Applying monthly filter to chart'); } showWeeklyData(chart) { console.log('Applying weekly filter to chart'); } showDailyData(chart) { console.log('Applying daily filter to chart'); } showAllData(chart) { console.log('Showing all data'); } setupChartExport() { const exportButtons = document.querySelectorAll('.chart-export'); exportButtons.forEach(button => { button.addEventListener('click', (e) => { const canvasId = e.target.getAttribute('data-chart'); const format = e.target.getAttribute('data-format'); if (format === 'image') { this.exportChartAsImage(canvasId); } else if (format === 'csv') { this.exportChartAsCSV(canvasId); } }); }); } setupChartControls() { const zoomInButtons = document.querySelectorAll('.chart-zoom-in'); const zoomOutButtons = document.querySelectorAll('.chart-zoom-out'); const resetButtons = document.querySelectorAll('.chart-reset'); zoomInButtons.forEach(button => { button.addEventListener('click', (e) => { const canvasId = e.target.getAttribute('data-chart'); this.zoomChart(canvasId, 'in'); }); }); zoomOutButtons.forEach(button => { button.addEventListener('click', (e) => { const canvasId = e.target.getAttribute('data-chart'); this.zoomChart(canvasId, 'out'); }); }); resetButtons.forEach(button => { button.addEventListener('click', (e) => { const canvasId = e.target.getAttribute('data-chart'); this.resetChartZoom(canvasId); }); }); } zoomChart(canvasId, direction) { const chart = this.charts.get(canvasId); if (!chart) return; const scales = chart.options.scales; if (!scales || !scales.x) return; if (direction === 'in') { if (scales.x.min === undefined) { const dataLength = chart.data.labels.length; scales.x.min = Math.floor(dataLength * 0.1); scales.x.max = Math.ceil(dataLength * 0.9); } else { const range = scales.x.max - scales.x.min; scales.x.min += Math.floor(range * 0.1); scales.x.max -= Math.floor(range * 0.1); } } else if (direction === 'out') { const dataLength = chart.data.labels.length; const currentRange = scales.x.max - scales.x.min; const newRange = Math.min(dataLength, currentRange * 1.2); const center = (scales.x.max + scales.x.min) / 2; scales.x.min = Math.max(0, Math.floor(center - newRange / 2)); scales.x.max = Math.min(dataLength, Math.ceil(center + newRange / 2)); } chart.update(); } resetChartZoom(canvasId) { const chart = this.charts.get(canvasId); if (!chart) return; const scales = chart.options.scales; if (scales && scales.x) { delete scales.x.min; delete scales.x.max; chart.update(); } } createDashboardCharts() { this.createVisitorsChart(); this.createRevenueChart(); this.createAnimalPopularityChart(); this.createTicketSalesChart(); } createVisitorsChart() { const data = { labels: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'], datasets: [{ label: 'Посетители', data: [1200, 1900, 1500, 2100, 2500, 3200, 3800, 3500, 3000, 2500, 1800, 1400], borderColor: '#2E7D32', backgroundColor: 'rgba(46, 125, 50, 0.1)', borderWidth: 2, fill: true, tension: 0.4 }] }; this.createChart('visitorsChart', { type: 'line', data: data, options: { plugins: { title: { display: true, text: 'Посетители по месяцам', font: { size: 16 } } } } }); } createRevenueChart() { const data = { labels: ['Билеты', 'Экскурсии', 'Сувениры', 'Питание', 'Фото'], datasets: [{ label: 'Доход (тыс. руб.)', data: [1200, 800, 400, 600, 300], backgroundColor: [ 'rgba(46, 125, 50, 0.8)', 'rgba(46, 125, 50, 0.7)', 'rgba(46, 125, 50, 0.6)', 'rgba(46, 125, 50, 0.5)', 'rgba(46, 125, 50, 0.4)' ], borderColor: '#2E7D32', borderWidth: 1 }] }; this.createChart('revenueChart', { type: 'bar', data: data, options: { plugins: { title: { display: true, text: 'Распределение доходов', font: { size: 16 } } } } }); } createAnimalPopularityChart() { const data = { labels: ['Львы', 'Слоны', 'Панды', 'Тигры', 'Жирафы', 'Пингвины'], datasets: [{ data: [25, 20, 15, 15, 12, 13], backgroundColor: [ '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD' ], borderWidth: 0 }] }; this.createChart('animalPopularityChart', { type: 'doughnut', data: data, options: { plugins: { title: { display: true, text: 'Популярность животных', font: { size: 16 } } } } }); } createTicketSalesChart() { const data = { labels: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'], datasets: [ { label: 'Взрослые', data: [65, 59, 80, 81, 56, 55, 40], backgroundColor: 'rgba(46, 125, 50, 0.5)', borderColor: '#2E7D32', borderWidth: 1 }, { label: 'Дети', data: [28, 48, 40, 19, 86, 27, 90], backgroundColor: 'rgba(33, 150, 243, 0.5)', borderColor: '#2196F3', borderWidth: 1 }, { label: 'Студенты', data: [18, 22, 25, 30, 35, 28, 20], backgroundColor: 'rgba(255, 152, 0, 0.5)', borderColor: '#FF9800', borderWidth: 1 } ] }; this.createChart('ticketSalesChart', { type: 'bar', data: data, options: { plugins: { title: { display: true, text: 'Продажи билетов по дням', font: { size: 16 } } }, scales: { x: { stacked: true, }, y: { stacked: true } } } }); } destroyAllCharts() { this.charts.forEach(chart => { chart.destroy(); }); this.charts.clear(); } } const chartManager = new ChartManager(); document.addEventListener('DOMContentLoaded', function() { chartManager.createDashboardCharts(); const refreshChartsBtn = document.getElementById('refreshCharts'); if (refreshChartsBtn) { refreshChartsBtn.addEventListener('click', () => { chartManager.destroyAllCharts(); chartManager.createDashboardCharts(); refreshChartsBtn.classList.add('rotate-animation'); setTimeout(() => { refreshChartsBtn.classList.remove('rotate-animation'); }, 1000); }); } const themeToggle = document.getElementById('chartThemeToggle'); if (themeToggle) { themeToggle.addEventListener('click', () => { const isDark = document.body.classList.contains('dark-theme'); chartManager.toggleChartTheme(isDark ? 'light' : 'dark'); }); } window.addEventListener('resize', () => { chartManager.charts.forEach(chart => { chart.resize(); }); }); });