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>