HTML для визуализации данных

Сложность: Продвинутый

Визуализация данных в HTML

Создание интерактивных диаграмм и графиков с использованием Canvas и SVG.

1. Интерактивная панель с диаграммами

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Визуализация данных - HTML5</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
            color: #333;
        }
        
        .dashboard {
            max-width: 1400px;
            margin: 0 auto;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            padding: 30px;
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
            backdrop-filter: blur(10px);
        }
        
        .header {
            text-align: center;
            margin-bottom: 40px;
        }
        
        .header h1 {
            font-size: 2.5em;
            background: linear-gradient(135deg, #667eea, #764ba2);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            margin-bottom: 10px;
        }
        
        .controls {
            display: flex;
            justify-content: center;
            gap: 15px;
            margin-bottom: 30px;
            flex-wrap: wrap;
        }
        
        .control-btn {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 25px;
            cursor: pointer;
            font-size: 14px;
            transition: all 0.3s ease;
            box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
        }
        
        .control-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
        }
        
        .charts-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 30px;
            margin-bottom: 30px;
        }
        
        .chart-container {
            background: white;
            border-radius: 15px;
            padding: 25px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            border: 1px solid rgba(0, 0, 0, 0.05);
        }
        
        .chart-title {
            font-size: 1.3em;
            font-weight: 600;
            margin-bottom: 20px;
            color: #2c3e50;
            text-align: center;
        }
        
        canvas {
            width: 100%;
            height: 300px;
            border-radius: 10px;
        }
        
        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        
        .stat-card {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            padding: 25px;
            border-radius: 15px;
            text-align: center;
            box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
        }
        
        .stat-value {
            font-size: 2.5em;
            font-weight: bold;
            margin: 10px 0;
        }
        
        .stat-label {
            font-size: 0.9em;
            opacity: 0.9;
        }
        
        .data-table {
            width: 100%;
            background: white;
            border-radius: 15px;
            overflow: hidden;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            margin-bottom: 30px;
        }
        
        .data-table table {
            width: 100%;
            border-collapse: collapse;
        }
        
        .data-table th,
        .data-table td {
            padding: 15px 20px;
            text-align: left;
            border-bottom: 1px solid #eee;
        }
        
        .data-table th {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            font-weight: 600;
        }
        
        .data-table tr:hover {
            background: #f8f9fa;
        }
        
        .tooltip {
            position: absolute;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 15px;
            border-radius: 5px;
            font-size: 14px;
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.3s;
            z-index: 1000;
        }
        
        @media (max-width: 768px) {
            .charts-grid {
                grid-template-columns: 1fr;
            }
            
            .chart-container {
                padding: 15px;
            }
            
            .controls {
                flex-direction: column;
                align-items: center;
            }
            
            .control-btn {
                width: 200px;
            }
        }
    </style>
</head>
<body>
    <div class="dashboard">
        <div class="header">
            <h1>? Панель визуализации данных</h1>
            <p>Интерактивные диаграммы и графики на HTML5 Canvas</p>
        </div>
        
        <div class="controls">
            <button class="control-btn" onclick="generateRandomData()">? Сгенерировать данные</button>
            <button class="control-btn" onclick="toggleAnimations()" id="animBtn">✨ Анимации: ВКЛ</button>
            <button class="control-btn" onclick="exportCharts()">? Экспорт PNG</button>
            <button class="control-btn" onclick="resetCharts()">? Сбросить</button>
        </div>
        
        <div class="stats-grid">
            <div class="stat-card">
                <div class="stat-label">Общий доход</div>
                <div class="stat-value" id="totalRevenue">$124,580</div>
                <div class="stat-label">+12.5% за месяц</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">Пользователи</div>
                <div class="stat-value" id="totalUsers">8,742</div>
                <div class="stat-label">+8.2% за месяц</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">Конверсия</div>
                <div class="stat-value" id="conversionRate">3.8%</div>
                <div class="stat-label">+0.4% за месяц</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">Средний чек</div>
                <div class="stat-value" id="avgOrder">$142</div>
                <div class="stat-label">+$15 за месяц</div>
            </div>
        </div>
        
        <div class="charts-grid">
            <div class="chart-container">
                <div class="chart-title">? Продажи по месяцам</div>
                <canvas id="lineChart"></canvas>
            </div>
            
            <div class="chart-container">
                <div class="chart-title">? Распределение по категориям</div>
                <canvas id="pieChart"></canvas>
            </div>
            
            <div class="chart-container">
                <div class="chart-title">? Сравнение показателей</div>
                <canvas id="barChart"></canvas>
            </div>
            
            <div class="chart-container">
                <div class="chart-title">? Прогресс по целям</div>
                <canvas id="radarChart"></canvas>
            </div>
        </div>
        
        <div class="data-table">
            <table>
                <thead>
                    <tr>
                        <th>Месяц</th>
                        <th>Продажи</th>
                        <th>Пользователи</th>
                        <th>Доход</th>
                        <th>Рост</th>
                    </tr>
                </thead>
                <tbody id="tableBody">
                    <!-- Данные будут добавлены через JavaScript -->
                </tbody>
            </table>
        </div>
    </div>

    <div class="tooltip" id="tooltip"></div>

    <script>
        // Данные для диаграмм
        let chartData = {
            months: ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"],
            sales: [12000, 19000, 15000, 25000, 22000, 30000, 28000, 32000, 35000, 40000, 38000, 45000],
            users: [4500, 5200, 4800, 6100, 5900, 7200, 6800, 7500, 8200, 8700, 8400, 9200],
            categories: ["Электроника", "Одежда", "Книги", "Спорт", "Красота"],
            categoryData: [35, 25, 15, 12, 13],
            targets: [85, 70, 90, 65, 80],
            actual: [78, 65, 88, 60, 75]
        };
        
        let animationsEnabled = true;
        
        // Инициализация диаграмм
        function initCharts() {
            drawLineChart();
            drawPieChart();
            drawBarChart();
            drawRadarChart();
            updateTable();
            updateStats();
        }
        
        // Линейная диаграмма
        function drawLineChart() {
            const canvas = document.getElementById("lineChart");
            const ctx = canvas.getContext("2d");
            
            // Очистка canvas
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            const padding = 60;
            const chartWidth = canvas.width - padding * 2;
            const chartHeight = canvas.height - padding * 2;
            
            // Находим максимальное значение для масштабирования
            const maxValue = Math.max(...chartData.sales);
            
            // Сетка и оси
            ctx.strokeStyle = "#e0e0e0";
            ctx.lineWidth = 1;
            
            // Горизонтальные линии
            for (let i = 0; i <= 5; i++) {
                const y = padding + (chartHeight / 5) * i;
                ctx.beginPath();
                ctx.moveTo(padding, y);
                ctx.lineTo(canvas.width - padding, y);
                ctx.stroke();
                
                // Подписи значений
                ctx.fillStyle = "#666";
                ctx.font = "12px Arial";
                ctx.textAlign = "right";
                ctx.fillText(Math.round(maxValue - (maxValue / 5) * i), padding - 10, y + 4);
            }
            
            // Вертикальные линии и подписи месяцев
            chartData.months.forEach((month, index) => {
                const x = padding + (chartWidth / (chartData.months.length - 1)) * index;
                
                ctx.beginPath();
                ctx.moveTo(x, padding);
                ctx.lineTo(x, canvas.height - padding);
                ctx.stroke();
                
                ctx.fillStyle = "#333";
                ctx.font = "12px Arial";
                ctx.textAlign = "center";
                ctx.fillText(month, x, canvas.height - padding + 20);
            });
            
            // Линия продаж
            ctx.beginPath();
            ctx.strokeStyle = "#667eea";
            ctx.lineWidth = 3;
            ctx.lineJoin = "round";
            
            chartData.sales.forEach((value, index) => {
                const x = padding + (chartWidth / (chartData.sales.length - 1)) * index;
                const y = canvas.height - padding - (value / maxValue) * chartHeight;
                
                if (index === 0) {
                    ctx.moveTo(x, y);
                } else {
                    ctx.lineTo(x, y);
                }
            });
            ctx.stroke();
            
            // Точки данных
            ctx.fillStyle = "#667eea";
            chartData.sales.forEach((value, index) => {
                const x = padding + (chartWidth / (chartData.sales.length - 1)) * index;
                const y = canvas.height - padding - (value / maxValue) * chartHeight;
                
                ctx.beginPath();
                ctx.arc(x, y, 5, 0, Math.PI * 2);
                ctx.fill();
            });
            
            // Легенда
            ctx.fillStyle = "#667eea";
            ctx.fillRect(canvas.width - 120, 20, 15, 15);
            ctx.fillStyle = "#333";
            ctx.font = "14px Arial";
            ctx.textAlign = "left";
            ctx.fillText("Продажи ($)", canvas.width - 100, 32);
        }
        
        // Круговая диаграмма
        function drawPieChart() {
            const canvas = document.getElementById("pieChart");
            const ctx = canvas.getContext("2d");
            
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;
            const radius = Math.min(centerX, centerY) - 40;
            
            const colors = ["#667eea", "#764ba2", "#f093fb", "#f5576c", "#4ecdc4"];
            const total = chartData.categoryData.reduce((sum, value) => sum + value, 0);
            
            let startAngle = 0;
            
            // Рисуем сегменты
            chartData.categoryData.forEach((value, index) => {
                const sliceAngle = (2 * Math.PI * value) / total;
                
                ctx.beginPath();
                ctx.moveTo(centerX, centerY);
                ctx.arc(centerX, centerY, radius, startAngle, startAngle + sliceAngle);
                ctx.closePath();
                
                ctx.fillStyle = colors[index];
                ctx.fill();
                
                // Добавляем обводку
                ctx.strokeStyle = "white";
                ctx.lineWidth = 2;
                ctx.stroke();
                
                startAngle += sliceAngle;
            });
            
            // Легенда
            const legendX = 30;
            const legendY = 30;
            
            chartData.categories.forEach((category, index) => {
                const y = legendY + index * 25;
                
                ctx.fillStyle = colors[index];
                ctx.fillRect(legendX, y, 15, 15);
                
                ctx.fillStyle = "#333";
                ctx.font = "12px Arial";
                ctx.textAlign = "left";
                ctx.fillText(`${category} (${chartData.categoryData[index]}%)`, legendX + 25, y + 12);
            });
        }
        
        // Столбчатая диаграмма
        function drawBarChart() {
            const canvas = document.getElementById("barChart");
            const ctx = canvas.getContext("2d");
            
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            const padding = 60;
            const chartWidth = canvas.width - padding * 2;
            const chartHeight = canvas.height - padding * 2;
            const barWidth = (chartWidth / chartData.months.length) * 0.6;
            
            const maxValue = Math.max(...chartData.sales, ...chartData.users);
            
            // Сетка
            ctx.strokeStyle = "#e0e0e0";
            ctx.lineWidth = 1;
            
            for (let i = 0; i <= 5; i++) {
                const y = padding + (chartHeight / 5) * i;
                ctx.beginPath();
                ctx.moveTo(padding, y);
                ctx.lineTo(canvas.width - padding, y);
                ctx.stroke();
            }
            
            // Столбцы
            chartData.months.forEach((month, index) => {
                const x = padding + (chartWidth / chartData.months.length) * index + (chartWidth / chartData.months.length - barWidth) / 2;
                
                // Продажи
                const salesHeight = (chartData.sales[index] / maxValue) * chartHeight;
                ctx.fillStyle = "rgba(102, 126, 234, 0.8)";
                ctx.fillRect(x, canvas.height - padding - salesHeight, barWidth / 2 - 2, salesHeight);
                
                // Пользователи
                const usersHeight = (chartData.users[index] / maxValue) * chartHeight;
                ctx.fillStyle = "rgba(118, 75, 162, 0.8)";
                ctx.fillRect(x + barWidth / 2 + 2, canvas.height - padding - usersHeight, barWidth / 2 - 2, usersHeight);
                
                // Подписи месяцев
                ctx.fillStyle = "#333";
                ctx.font = "12px Arial";
                ctx.textAlign = "center";
                ctx.fillText(month, x + barWidth / 2, canvas.height - padding + 20);
            });
            
            // Легенда
            ctx.fillStyle = "rgba(102, 126, 234, 0.8)";
            ctx.fillRect(canvas.width - 120, 20, 15, 15);
            ctx.fillStyle = "#333";
            ctx.font = "12px Arial";
            ctx.textAlign = "left";
            ctx.fillText("Продажи", canvas.width - 100, 32);
            
            ctx.fillStyle = "rgba(118, 75, 162, 0.8)";
            ctx.fillRect(canvas.width - 120, 45, 15, 15);
            ctx.fillStyle = "#333";
            ctx.fillText("Пользователи", canvas.width - 100, 57);
        }
        
        // Радарная диаграмма
        function drawRadarChart() {
            const canvas = document.getElementById("radarChart");
            const ctx = canvas.getContext("2d");
            
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;
            const radius = Math.min(centerX, centerY) - 40;
            const segments = 5;
            
            // Сетка
            ctx.strokeStyle = "#e0e0e0";
            ctx.lineWidth = 1;
            
            for (let i = 1; i <= 3; i++) {
                const currentRadius = (radius / 3) * i;
                ctx.beginPath();
                
                for (let j = 0; j <= segments; j++) {
                    const angle = (Math.PI * 2 * j) / segments - Math.PI / 2;
                    const x = centerX + currentRadius * Math.cos(angle);
                    const y = centerY + currentRadius * Math.sin(angle);
                    
                    if (j === 0) {
                        ctx.moveTo(x, y);
                    } else {
                        ctx.lineTo(x, y);
                    }
                }
                
                ctx.closePath();
                ctx.stroke();
            }
            
            // Линии осей
            for (let i = 0; i < segments; i++) {
                const angle = (Math.PI * 2 * i) / segments - Math.PI / 2;
                const x = centerX + radius * Math.cos(angle);
                const y = centerY + radius * Math.sin(angle);
                
                ctx.beginPath();
                ctx.moveTo(centerX, centerY);
                ctx.lineTo(x, y);
                ctx.stroke();
            }
            
            // Данные целей
            ctx.fillStyle = "rgba(102, 126, 234, 0.6)";
            ctx.strokeStyle = "#667eea";
            ctx.lineWidth = 2;
            
            ctx.beginPath();
            chartData.targets.forEach((value, index) => {
                const angle = (Math.PI * 2 * index) / segments - Math.PI / 2;
                const pointRadius = (value / 100) * radius;
                const x = centerX + pointRadius * Math.cos(angle);
                const y = centerY + pointRadius * Math.sin(angle);
                
                if (index === 0) {
                    ctx.moveTo(x, y);
                } else {
                    ctx.lineTo(x, y);
                }
            });
            ctx.closePath();
            ctx.fill();
            ctx.stroke();
            
            // Данные фактических значений
            ctx.fillStyle = "rgba(118, 75, 162, 0.6)";
            ctx.strokeStyle = "#764ba2";
            ctx.lineWidth = 2;
            
            ctx.beginPath();
            chartData.actual.forEach((value, index) => {
                const angle = (Math.PI * 2 * index) / segments - Math.PI / 2;
                const pointRadius = (value / 100) * radius;
                const x = centerX + pointRadius * Math.cos(angle);
                const y = centerY + pointRadius * Math.sin(angle);
                
                if (index === 0) {
                    ctx.moveTo(x, y);
                } else {
                    ctx.lineTo(x, y);
                }
            });
            ctx.closePath();
            ctx.fill();
            ctx.stroke();
            
            // Подписи
            const labels = ["Продажи", "Маркетинг", "Разработка", "Поддержка", "Аналитика"];
            ctx.fillStyle = "#333";
            ctx.font = "12px Arial";
            ctx.textAlign = "center";
            
            labels.forEach((label, index) => {
                const angle = (Math.PI * 2 * index) / segments - Math.PI / 2;
                const x = centerX + (radius + 20) * Math.cos(angle);
                const y = centerY + (radius + 20) * Math.sin(angle);
                
                ctx.fillText(label, x, y);
            });
        }
        
        // Обновление таблицы
        function updateTable() {
            const tableBody = document.getElementById("tableBody");
            tableBody.innerHTML = "";
            
            chartData.months.forEach((month, index) => {
                const growth = index > 0 ? 
                    ((chartData.sales[index] - chartData.sales[index - 1]) / chartData.sales[index - 1] * 100).toFixed(1) : 
                    "0.0";
                
                const row = `
                    <tr>
                        <td>${month}</td>
                        <td>${chartData.sales[index].toLocaleString()}</td>
                        <td>${chartData.users[index].toLocaleString()}</td>
                        <td>$${(chartData.sales[index] * 0.3).toLocaleString()}</td>
                        <td style="color: ${growth >= 0 ? "#27ae60" : "#e74c3c"}">${growth}%</td>
                    </tr>
                `;
                tableBody.innerHTML += row;
            });
        }
        
        // Обновление статистики
        function updateStats() {
            const totalRevenue = chartData.sales.reduce((sum, sales) => sum + sales, 0) * 0.3;
            const totalUsers = chartData.users[chartData.users.length - 1];
            const avgOrder = totalRevenue / chartData.sales.reduce((sum, sales) => sum + sales, 0) * 1000;
            
            document.getElementById("totalRevenue").textContent = `$${(totalRevenue / 1000).toFixed(0)}K`;
            document.getElementById("totalUsers").textContent = totalUsers.toLocaleString();
            document.getElementById("conversionRate").textContent = "3.8%";
            document.getElementById("avgOrder").textContent = `$${avgOrder.toFixed(0)}`;
        }
        
        // Генерация случайных данных
        function generateRandomData() {
            chartData.sales = chartData.sales.map(() => Math.floor(Math.random() * 30000) + 10000);
            chartData.users = chartData.users.map(() => Math.floor(Math.random() * 5000) + 4000);
            chartData.categoryData = chartData.categoryData.map(() => Math.floor(Math.random() * 20) + 10);
            
            // Нормализуем проценты категорий
            const total = chartData.categoryData.reduce((sum, val) => sum + val, 0);
            chartData.categoryData = chartData.categoryData.map(val => Math.round((val / total) * 100));
            
            initCharts();
        }
        
        // Переключение анимаций
        function toggleAnimations() {
            animationsEnabled = !animationsEnabled;
            document.getElementById("animBtn").textContent = 
                `✨ Анимации: ${animationsEnabled ? "ВКЛ" : "ВЫКЛ"}`;
        }
        
        // Экспорт диаграмм
        function exportCharts() {
            const charts = ["lineChart", "pieChart", "barChart", "radarChart"];
            
            charts.forEach((chartId, index) => {
                const canvas = document.getElementById(chartId);
                const link = document.createElement("a");
                link.download = `chart_${index + 1}.png`;
                link.href = canvas.toDataURL();
                link.click();
            });
        }
        
        // Сброс диаграмм
        function resetCharts() {
            // Восстанавливаем исходные данные
            chartData = {
                months: ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"],
                sales: [12000, 19000, 15000, 25000, 22000, 30000, 28000, 32000, 35000, 40000, 38000, 45000],
                users: [4500, 5200, 4800, 6100, 5900, 7200, 6800, 7500, 8200, 8700, 8400, 9200],
                categories: ["Электроника", "Одежда", "Книги", "Спорт", "Красота"],
                categoryData: [35, 25, 15, 12, 13],
                targets: [85, 70, 90, 65, 80],
                actual: [78, 65, 88, 60, 75]
            };
            
            initCharts();
        }
        
        // Инициализация при загрузке
        window.addEventListener("load", initCharts);
    </script>
</body>
</html>