Разработка тепловых карт данных (Heatmap) для сайта

Вы тратите часы на анализ таблиц с тысячами строк, пытаясь найти закономерности. Тепловая карта сжимает эту информацию в один экран: красные зоны — пики активности, зелёные — низкая. За две секунды вы видите, что чаще всего заказы делают в пятницу вечером. Но выбор правильного стека — непростая зада

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Разработка тепловых карт данных (Heatmap) для сайта
Средний
от 1 дня до 3 дней

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1422
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    984
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1250
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    986
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    1000

Вы тратите часы на анализ таблиц с тысячами строк, пытаясь найти закономерности. Тепловая карта сжимает эту информацию в один экран: красные зоны — пики активности, зелёные — низкая. За две секунды вы видите, что чаще всего заказы делают в пятницу вечером. Но выбор правильного стека — непростая задача: SVG тормозит на 10 000+ точек, Canvas требует ручного hit-testing, а интеграция с бэкендом может затянуться. Мы решаем эту задачу с нуля: от подготовки данных до публикации на продакшене. Наши инженеры используют современный стек: React, D3.js, Leaflet, Canvas — и интегрируют эти решения с вашей бэкенд-системой. Результат: вы видите паттерны там, где таблицы требуют минут анализа.

Какие задачи решаем?

Тепловая карта — универсальный инструмент, но важно выбрать правильный тип визуализации. Рассмотрим четыре ключевых варианта.

Временная активность

Строки — дни недели, столбцы — часы. Классика для показа, когда происходят события (заказы, визиты, инциденты). GitHub contribution graph — именно такая карта. Помогает выявить пиковые часы нагрузки и спланировать ресурсы. Пример: для интернет-магазина мы построили heatmap активности по дням и часам, что позволило выявить пик заказов в 20:00 по пятницам. После запуска таргетированных push-уведомлений конверсия выросла на 15%.

Матрица корреляций

N×N ячеек, значение от -1 до 1. Используется в финансах и ML для анализа зависимостей между переменными. Например, связь между запуском рекламы и конверсией.

Географический heatmap

Наложение плотности точек на карту. Решается через Leaflet + leaflet.heat или Mapbox. Полезен для логистики, ритейла, анализа посещаемости.

Cohort retention

Строки — когорты (месяц регистрации), столбцы — периоды удержания. Один из ключевых инструментов product analytics. Позволяет понять, как долго пользователи остаются активными после регистрации. Мы фиксируем домен цветовой шкалы от 0 до 100%, чтобы избежать искажений.

Сводка типов heatmap:

Тип Данные Пример использования
Временная активность (день, час, количество) GitHub contribution graph
Матрица корреляций (переменная, переменная, коэффициент) Финансовые корреляции
Географический (широта, долгота, вес) Распределение клиентов по точкам
Cohort retention (когорта, период, %) Удержание пользователей после регистрации

Как выбрать между SVG и Canvas?

Производительность — главный критерий. D3.js documentation рекомендует Canvas для больших объёмов данных. SVG-heatmap из 10 000+ ячеек тормозит: каждый элемент — отдельный DOM-узел. Canvas рендерит быстрее, но требует ручного hit-testing для tooltip. Мы выбираем технологию под задачу.

Характеристика SVG Canvas
Количество ячеек до 3 000 10 000+
Tooltip встроенный ручной hit-test
Масштабирование без потерь с потерей
Разработка быстрее сложнее

Для больших объёмов используем Canvas. Пример hit-testing:

useEffect(() => { const canvas = canvasRef.current!; const ctx = canvas.getContext('2d')!; const dpr = window.devicePixelRatio; canvas.width = width * dpr; canvas.height = height * dpr; canvas.style.width = `${width}px`; canvas.style.height = `${height}px`; ctx.scale(dpr, dpr); const cellW = iw / cols.length; const cellH = ih / rows.length; data.forEach(d => { const x = margin.left + cols.indexOf(d.col) * cellW; const y = margin.top + rows.indexOf(d.row) * cellH; ctx.fillStyle = colorScale(d.value); ctx.fillRect(x + 1, y + 1, cellW - 2, cellH - 2); }); }, [data]); 

Реализация через D3.js

import { useEffect, useRef } from 'react'; import * as d3 from 'd3'; interface HeatmapCell { row: string; col: string; value: number; } interface HeatmapProps { data: HeatmapCell[]; rows: string[]; cols: string[]; colorScheme?: 'blues' | 'reds' | 'greens' | 'rdylgn'; width?: number; height?: number; } export function Heatmap({ data, rows, cols, colorScheme = 'blues', width = 700, height = 400 }: HeatmapProps) { const svgRef = useRef<SVGSVGElement>(null); const margin = { top: 30, right: 20, bottom: 60, left: 80 }; const iw = width - margin.left - margin.right; const ih = height - margin.top - margin.bottom; useEffect(() => { if (!svgRef.current) return; const svg = d3.select(svgRef.current); svg.selectAll('*').remove(); const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`); const xScale = d3.scaleBand().domain(cols).range([0, iw]).padding(0.05); const yScale = d3.scaleBand().domain(rows).range([0, ih]).padding(0.05); const colorInterpolators = { blues: d3.interpolateBlues, reds: d3.interpolateReds, greens: d3.interpolateGreens, rdylgn: d3.interpolateRdYlGn, }; const extent = d3.extent(data, d => d.value) as [number, number]; const colorScale = d3.scaleSequential() .domain(extent) .interpolator(colorInterpolators[colorScheme]); // Axes g.append('g') .attr('transform', `translate(0,${ih})`) .call(d3.axisBottom(xScale).tickSize(0)) .select('.domain').remove(); g.append('g') .call(d3.axisLeft(yScale).tickSize(0)) .select('.domain').remove(); // Tooltip const tooltip = d3.select('body').append('div') .style('position', 'absolute') .style('display', 'none') .style('background', 'rgba(0,0,0,0.8)') .style('color', '#fff') .style('padding', '6px 10px') .style('border-radius', '4px') .style('font-size', '12px') .style('pointer-events', 'none'); // Cells g.selectAll('.cell') .data(data) .join('rect') .attr('class', 'cell') .attr('x', d => xScale(d.col)!) .attr('y', d => yScale(d.row)!) .attr('width', xScale.bandwidth()) .attr('height', yScale.bandwidth()) .attr('fill', d => d.value == null ? '#f0f0f0' : colorScale(d.value)) .attr('rx', 2) .on('mouseover', (event, d) => { tooltip .style('display', 'block') .style('left', `${event.pageX + 12}px`) .style('top', `${event.pageY - 28}px`) .html(`<strong>${d.row} / ${d.col}</strong><br/>${d3.format(',.2f')(d.value)}`); }) .on('mouseout', () => tooltip.style('display', 'none')); return () => { tooltip.remove(); }; }, [data, rows, cols, colorScheme]); return <svg ref={svgRef} width={width} height={height} />; } 

Легенда с градиентом

function addColorLegend( svg: d3.Selection<SVGSVGElement, unknown, null, undefined>, colorScale: d3.ScaleSequential<string>, x: number, y: number, width = 200, height = 12 ) { const defs = svg.append('defs'); const gradientId = `legend-gradient-${Math.random().toString(36).slice(2)}`; const gradient = defs.append('linearGradient').attr('id', gradientId); gradient.append('stop').attr('offset', '0%').attr('stop-color', colorScale(colorScale.domain()[0])); gradient.append('stop').attr('offset', '100%').attr('stop-color', colorScale(colorScale.domain()[1])); const legendG = svg.append('g').attr('transform', `translate(${x},${y})`); legendG.append('rect') .attr('width', width) .attr('height', height) .style('fill', `url(#${gradientId})`); const legendScale = d3.scaleLinear() .domain(colorScale.domain()) .range([0, width]); legendG.append('g') .attr('transform', `translate(0,${height})`) .call(d3.axisBottom(legendScale).ticks(4).tickFormat(d3.format(',.0f'))); } 

Cohort retention heatmap

Частный случай с особой логикой — значения по диагонали от 0% до 100%:

interface CohortRow { cohort: string; // "Jan 2024" periods: (number | null)[]; // retention % по периодам } function prepareCohortData(cohorts: CohortRow[]): HeatmapCell[] { return cohorts.flatMap((row, rowIdx) => row.periods.map((value, colIdx) => ({ row: row.cohort, col: `Period ${colIdx}`, value: value ?? 0, isEmpty: value === null, })) ).filter(d => !d.isEmpty); } 

Для retention colorScale лучше использовать d3.interpolateRdYlGn — красный для низких значений, зелёный для высоких. Домен фиксируем [0, 100], а не от min до max, иначе визуализация вводит в заблуждение.

Почему фиксированный домен важен?

Если домен динамический, разница между 80% и 90% будет показана ярко, хотя обе цифры высоки. Фиксированный домен [0,100] даёт объективную картину удержания.

Как интегрировать heatmap с вашим API?

Для больших временных диапазонов данные агрегируются на сервере. Используем PostgreSQL и Redis для кеширования, что снижает нагрузку на API. Например, для активности по дням недели и часам применяем SQL-запрос:

SELECT EXTRACT(DOW FROM created_at)::int AS dow, EXTRACT(HOUR FROM created_at)::int AS hour, COUNT(*) AS events FROM user_events WHERE created_at > NOW() - INTERVAL '90 days' AND user_id = $1 GROUP BY 1, 2 ORDER BY 1, 2; 

Результат передаётся по API, а пустые ячейки заполняются нулями на сервере. Мы интегрируемся с любым бэкендом: Laravel, Node.js, Python. При необходимости настраиваем обновление в реальном времени через WebSocket.

Что входит в работу?

  • Документация API и архитектуры решения
  • Исходный код с комментариями
  • Адаптация под вашу CMS (WordPress, Drupal, Strapi)
  • Инструкция по дальнейшей поддержке
  • Гарантия 1 месяц на скрытые ошибки

Сроки

Базовый heatmap с tooltip и легендой — 1–2 дня. Cohort retention с правильной подготовкой данных — 3–5 дней. Географический heatmap — отдельная оценка. Стоимость рассчитывается индивидуально.

Наша компания более 5 лет на рынке, реализовала 30+ проектов с heatmap. Опыт с Core Web Vitals гарантирует, что ваша карта не замедлит сайт. Закажите консультацию — наши инженеры проанализируют ваши данные и предложат оптимальное решение. Свяжитесь с нами, чтобы получить расчёт под ключ.