Разработка Treemap-диаграмм для визуализации иерархий

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

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

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

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

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

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

Этапы разработки

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

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

Представьте: у вас дашборд с бюджетом компании на 500+ статей расходов. Вы тратите 20 минут на поиск, где «дыра». Наш treemap показывает это за 2 секунды. Treemap делает одну вещь хорошо: показывает соотношение частей в иерархической структуре. Площадь прямоугольника пропорциональна значению, цвет — дополнительное измерение: рост/падение, категория, статус. Если нужно понять, что занимает больше всего места в бюджете, дереве категорий, файловой системе или портфеле активов, — treemap читается быстрее любой таблицы. При этом разработка на React 18 и D3.js v7 даёт полный контроль над каждым пикселем: кастомный tooltip, анимация при drill-down, адаптивная вёрстка под любое разрешение. Даже на 10 000 узлах layout строится за десятки миллисекунд — достаточно для real-time обновлений.

Какие задачи решает treemap-диаграмма?

Плохая читаемость таблиц с иерархией. Когда в таблице 10 000 строк, найти топ-5 категорий можно только скроллом и сортировкой. Treemap показывает их визуально — мозг обрабатывает площадь мгновенно.

Сложность восприятия пропорций. В таблице числа, но чтобы понять, насколько бюджет маркетинга больше бюджета разработки, нужно делить. Treemap даёт ответ без вычислений.

Необходимость сравнивать доли в реальном времени. При обновлении данных (например, котировки акций) treemap с цветовым кодированием изменений показывает, какие активы растут, а какие падают. Достаточно взгляда.

Как мы реализуем treemap на React и D3.js?

Мы используем React 18 и D3.js v7. D3 — это не библиотека диаграмм, а швейцарский нож для работы с данными и SVG. Он даёт полный контроль над каждым прямоугольником. Мы не используем high-level обёртки, потому что каждый проект требует кастомных решений: особый tooltip, анимация при drill-down, адаптивная вёрстка.

Вот типичная реализация компонента treemap:

import { useEffect, useRef } from 'react';
import * as d3 from 'd3';

interface TreeNode {
  name: string;
  value?: number;
  children?: TreeNode[];
  change?: number; // % изменения для цветового кодирования
}

interface TreemapProps {
  data: TreeNode;
  width?: number;
  height?: number;
  colorBy?: 'category' | 'change';
}

export function Treemap({ data, width = 800, height = 500, colorBy = 'category' }: TreemapProps) {
  const svgRef = useRef<SVGSVGElement>(null);

  useEffect(() => {
    if (!svgRef.current) return;

    const svg = d3.select(svgRef.current);
    svg.selectAll('*').remove();

    // Иерархия
    const root = d3.hierarchy(data)
      .sum(d => d.value ?? 0)
      .sort((a, b) => (b.value ?? 0) - (a.value ?? 0));

    // Layout
    d3.treemap()
      .size([width, height])
      .paddingOuter(3)
      .paddingInner(2)
      .paddingTop(18)
      .round(true)(root);

    const colorScale = colorBy === 'change'
      ? d3.scaleDiverging(d3.interpolateRdYlGn).domain([-30, 0, 30])
      : d3.scaleOrdinal(d3.schemeTableau10);

    const tooltip = d3.select('body').append('div')
      .style('position', 'absolute')
      .style('display', 'none')
      .style('background', 'rgba(15,23,42,0.9)')
      .style('color', '#f1f5f9')
      .style('padding', '8px 12px')
      .style('border-radius', '4px')
      .style('font-size', '13px')
      .style('pointer-events', 'none')
      .style('max-width', '220px');

    // Все узлы с потомками (для группировок)
    const leaves = root.leaves();
    const ancestors = root.descendants().filter(d => d.depth === 1);

    // Фоновые прямоугольники для групп
    svg.selectAll('.group-rect')
      .data(ancestors)
      .join('rect')
      .attr('class', 'group-rect')
      .attr('x', (d: any) => d.x0)
      .attr('y', (d: any) => d.y0)
      .attr('width', (d: any) => d.x1 - d.x0)
      .attr('height', (d: any) => d.y1 - d.y0)
      .attr('fill', (d: any) => colorBy === 'change' ? '#e2e8f0' : d3.color(colorScale(d.data.name))!.brighter(0.7).toString())
      .attr('stroke', '#fff')
      .attr('stroke-width', 2);

    // Заголовки групп
    svg.selectAll('.group-label')
      .data(ancestors)
      .join('text')
      .attr('class', 'group-label')
      .attr('x', (d: any) => d.x0 + 6)
      .attr('y', (d: any) => d.y0 + 13)
      .attr('font-size', 11)
      .attr('font-weight', '600')
      .attr('fill', '#374151')
      .text((d: any) => d.data.name);

    // Листья
    const cell = svg.selectAll('.cell')
      .data(leaves)
      .join('g')
      .attr('class', 'cell')
      .attr('transform', (d: any) => `translate(${d.x0},${d.y0})`);

    cell.append('rect')
      .attr('width', (d: any) => d.x1 - d.x0)
      .attr('height', (d: any) => d.y1 - d.y0)
      .attr('fill', (d: any) => {
        if (colorBy === 'change') return colorScale(d.data.change ?? 0);
        return colorScale((d.parent?.data.name ?? '') as string);
      })
      .attr('fill-opacity', 0.85)
      .attr('stroke', '#fff')
      .attr('stroke-width', 1)
      .on('mouseover', (event, d: any) => {
        d3.select(event.currentTarget).attr('fill-opacity', 1);
        const pct = d.data.change != null
          ? `<br/>Изменение: ${d.data.change > 0 ? '+' : ''}${d.data.change.toFixed(1)}%`
          : '';
        tooltip
          .style('display', 'block')
          .style('left', `${event.pageX + 12}px`)
          .style('top', `${event.pageY - 28}px`)
          .html(`<strong>${d.data.name}</strong><br/>${d3.format(',.0f')(d.value ?? 0)}${pct}`);
      })
      .on('mouseout', (event) => {
        d3.select(event.currentTarget).attr('fill-opacity', 0.85);
        tooltip.style('display', 'none');
      });

    // Текст внутри ячеек (только если достаточно места)
    cell.append('text')
      .attr('x', 4)
      .attr('y', 14)
      .attr('font-size', 11)
      .attr('fill', '#fff')
      .attr('font-weight', '500')
      .text((d: any) => {
        const w = d.x1 - d.x0;
        const h = d.y1 - d.y0;
        return w > 40 && h > 20 ? d.data.name : '';
      })
      .each(function(d: any) {
        const el = d3.select(this);
        const maxWidth = d.x1 - d.x0 - 8;
        // Обрезаем текст если не помещается
        let text = d.data.name;
        while (this.getComputedTextLength() > maxWidth && text.length > 3) {
          text = text.slice(0, -1);
          el.text(text + '…');
        }
      });

    // Значение под названием
    cell.append('text')
      .attr('x', 4)
      .attr('y', 26)
      .attr('font-size', 10)
      .attr('fill', 'rgba(255,255,255,0.8)')
      .text((d: any) => {
        const w = d.x1 - d.x0;
        const h = d.y1 - d.y0;
        return w > 50 && h > 35 ? d3.format(',.0f')(d.value ?? 0) : '';
      });

    return () => { tooltip.remove(); };
  }, [data, width, height, colorBy]);

  return <svg ref={svgRef} width={width} height={height} style={{ display: 'block' }} />;
}
Описание реализации В коде выше мы создаём иерархию через d3.hierarchy, настраиваем layout с отступами, рендерим фоновые прямоугольники для групп, заголовки, ячейки с tooltip и текстом. Адаптивное поведение гарантирует читаемость на любом экране.

Как добавить drill-down?

Drill-down — ключевая функция для интерактивных дашбордов. Пользователь кликает по группе — и treemap перерисовывается с данными дочернего уровня. Мы реализовали компонент DrilldownTreemap с хлебными крошками для навигации назад.

function DrilldownTreemap({ data }: { data: TreeNode }) {
  const [currentNode, setCurrentNode] = useState<TreeNode>(data);
  const [breadcrumb, setBreadcrumb] = useState<TreeNode[]>([data]);

  function drillDown(node: TreeNode) {
    if (!node.children?.length) return;
    setCurrentNode(node);
    setBreadcrumb(prev => [...prev, node]);
  }

  function drillUp(index: number) {
    const target = breadcrumb[index];
    setCurrentNode(target);
    setBreadcrumb(prev => prev.slice(0, index + 1));
  }

  return (
    <div>
      <nav className="flex gap-2 text-sm mb-3">
        {breadcrumb.map((node, i) => (
          <span key={i}>
            {i > 0 && <span className="text-gray-400 mx-1">/</span>}
            <button
              onClick={() => drillUp(i)}
              className={i === breadcrumb.length - 1 ? 'font-semibold' : 'text-blue-600 hover:underline'}
            >
              {node.name}
            </button>
          </span>
        ))}
      </nav>
      <Treemap data={currentNode} onCellClick={drillDown} />
    </div>
  );
}

Какой алгоритм раскладки выбрать?

D3.js предоставляет четыре тайлинг-алгоритма. Сравнение:

Алгоритм Особенность Когда использовать
treemapSquarify Ячейки максимально квадратные, хороший aspect ratio По умолчанию — лучшая читаемость
treemapSliceDice Чередует горизонтальные и вертикальные срезы Когда данные имеют строгую иерархию по двум направлениям
treemapSlice Только горизонтальные срезы Для отображения долей в одной строке
treemapResquarify Переиспользует прошлую раскладку, минимизируя перемещения Для анимированного обновления данных

Мы всегда используем treemapSquarify в production — он даёт наилучшее соотношение сторон. Если необходима плавная смена данных (например, при обновлении котировок), переключаемся на treemapResquarify. Как отмечается в документации D3.js Hierarchy, алгоритм Squarify обеспечивает оптимальный aspect ratio для большинства случаев.

Как подготовить данные для treemap?

Чтобы treemap работал, данные на клиенте должны быть преобразованы в иерархию. Часто API возвращает плоский список строк. Функция buildHierarchy группирует их по категориям и подкатегориям:

// Трансформация плоских данных в иерархию
function buildHierarchy(items: { category: string; subcategory: string; name: string; value: number }[]): TreeNode {
  const root: TreeNode = { name: 'root', children: [] };

  items.forEach(item => {
    let cat = root.children!.find(c => c.name === item.category);
    if (!cat) {
      cat = { name: item.category, children: [] };
      root.children!.push(cat);
    }
    let subcat = cat.children!.find(c => c.name === item.subcategory);
    if (!subcat) {
      subcat = { name: item.subcategory, children: [] };
      cat.children!.push(subcat);
    }
    subcat.children!.push({ name: item.name, value: item.value });
  });

  return root;
}

Процесс работы и сроки

  1. Аналитика — изучаем структуру данных, определяем уровни иерархии, метрики для площади и цвета. (1 день)
  2. Проектирование — создаём прототип на основе ваших данных, согласовываем дизайн и интерактив. (1 день)
  3. Реализация — пишем компонент на React + D3.js, настраиваем tooltip, drill-down, анимацию. (2-3 дня)
  4. Тестирование — проверяем на реальных данных: большие объёмы, обновления в реальном времени. (1 день)
  5. Деплой — интегрируем в ваш проект, даём инструкцию по использованию. (1 день)
Готовность Срок
Базовый treemap с tooltip и drill-down 2–3 дня
С анимацией переходов и цветовым кодированием 4–6 дней
Полный цикл (аналитика → деплой) от 1 недели

Какие ошибки допускают при разработке treemap?

  • Игнорирование paddingTop. Заголовки групп накладываются на ячейки — текст нечитаем. Мы всегда задаём paddingTop: 18.
  • Текст в слишком маленьких ячейках. Проверяем ширину и высоту перед рендером, иначе — переполнение.
  • Отсутствие обработки пустых данных. Если узел с value = 0, он всё равно занимает место. Используем фильтрацию.
  • Игнорирование treemapResquarify при анимации. Без него каждый ререндер перемешивает ячейки — пользователь теряет контекст.

Что входит в работу и почему выбирают нас

  • Исходный код компонента (React + TypeScript + D3)
  • Интеграция с вашим API
  • Документация по настройке и кастомизации
  • Инструкция по развёртыванию
  • Обучение команды (до 2 часов)
  • Гарантия 3 месяца на бесперебойную работу

Мы имеем 5+ лет опыта в визуализации данных на сложных интерфейсах и 30+ реализованных проектов с treemap, sunburst и другими иерархическими диаграммами. Наши разработчики сертифицированы по React и D3.js, а все компоненты создаются с нуля, без рисков лицензирования. Свяжитесь с нами для консультации и закажите разработку treemap, который заговорит сам за себя. Получите бесплатную оценку вашего проекта за 1 рабочий день — просто пришлите пример данных и требования по интерактиву.

Настройка веб-аналитики: GA4, GTM, Яндекс.Метрика и Amplitude

Мы часто видим: конверсия 1.2 %, трафик растёт, а конверсия стоит. Маркетолог смотрит в Google Analytics и говорит: «пользователи уходят с шага 2 оформления заказа». Разработчик открывает тот же шаг — ошибок нет, в Sentry тишина. Значит, дело не в JS-баге, а в UX или в кривых данных, которые показывает аналитика. Аналитика ломается незаметно: событие перестало трекаться после редеплоя — никто не заметил; GTM-тег стреляет дважды — данные задвоились; фильтр GA4 исключает бота, который на самом деле — реальный трафик с корпоративного прокси. Закажите аудит текущих тегов — мы найдём причину за неделю.

После правильной настройки экономия рекламного бюджета может достигать 150 000 ₽ в месяц — это реальный кейс интернет-магазина с 50 000 сессий в день, где дедупликация purchase вернула 20 % неверно приписанных конверсий.

Почему события GA4 дублируются и как это исправить?

Universal Analytics закрыт, его место заняла событийная модель GA4. В ней нет фиксированных хитов страниц и транзакций — только события с параметрами. Это гибче, но требует правильного дизайна событий.

Автоматические события GA4 собирает сам: page_view, scroll, click, session_start. Рекомендуемые события нужно реализовать самостоятельно: purchase, add_to_cart, begin_checkout, view_item. Google ожидает конкретную схему параметров — если передать product_id вместо item_id, данные попадут в GA4, но не в стандартные отчёты e-commerce. Кастомные события для специфики проекта: filter_applied, video_progress, form_step_completed. Кастомные параметры необходимо зарегистрировать в GA4 Admin → Custom definitions, иначе они не будут доступны в отчётах.

Частая ошибка — событие purchase с дублями. Причина: тег срабатывает на странице /thank-you, пользователь обновляет страницу — второй purchase уходит в GA4. Решение: на бэкенде генерируем уникальный transaction_id и передаём в событие. GA4 de-duplicates по нему (в теории — проверяйте через DebugView). Правильная атрибуция экономит до 20 % рекламного бюджета, который раньше уходил на неверно приписанные конверсии.

Как настроить data layer, чтобы не потерять данные?

GTM — инструмент для управления тегами без деплоя кода. Но «без кода» не значит «без архитектуры». Data Layer — основа всего. Передаём данные из приложения в GTM через dataLayer.push(). Структура: event + контекстные данные. Для e-commerce: перед открытием страницы продукта — push с данными товара. GTM-тег читает из dataLayer, не из DOM.

window.dataLayer = window.dataLayer || [];
dataLayer.push({
  event: 'view_item',
  ecommerce: {
    items: [{
      item_id: 'SKU-12345',
      item_name: 'Название товара',
      price: 1990.00,
      currency: 'RUB'
    }]
  }
});

Плохая практика: GTM-тег парсит DOM — ищет цену в span.price, название в h1. Это ломается при любом изменении верстки. Хорошая практика: всегда dataLayer. Используем Preview Mode для отладки и GTM Server-Side для чувствительных данных — отправка с сервера, не с браузера, обходит блокировщики рекламы, не теряет данные.

Как Яндекс.Метрика дополняет веб-аналитику?

Для российской аудитории Метрика обязательна — особенно Вебвизор. Запись сессии пользователя, который бросил корзину, часто даёт ответ быстрее, чем неделя анализа воронки. Цели в Метрике: событийные (через ym(COUNTER_ID, 'reachGoal', 'GOAL_NAME')) или автоматические (клик по кнопке, посещение страницы). Связка с CRM через Метрика Плюс — передача офлайн-конверсий. Наш опыт: в 8 из 10 проектов после настройки Метрики находили скрытые баги в UX, которые не показывали другие системы.

Что даёт product analytics в Amplitude?

Amplitude — продуктовый инструмент, в отличие от маркетинговых GA4 и Метрики. Он заточен под анализ поведения пользователей внутри продукта: воронки, ретеншн, user paths. Amplitude подходит для SaaS-продуктов, мобильных приложений и любых сервисов с зарегистрированными пользователями, где важно понять, как проходят онбординг, на каком шаге уходят, какие фичи используют чаще. Ключевые концепции: identify (связать анонимного пользователя с userId после авторизации), group (аккаунт в B2B SaaS), когорты для удержания. Amplitude Chart — воронка шагов за последние 30 дней с разбивкой по источнику.

Мониторинг качества данных

Аналитика без мониторинга — чёрный ящик. Настраиваем:

  • GA4 Realtime — проверяем после каждого деплоя, что ключевые события приходят
  • Alerting в GA4 — аномалия в количестве событий purchase (резкое падение = что-то сломалось)
  • GTM Preview в staging-окружении перед продакшеном
  • Ручные тесты воронок раз в неделю — просто пройти путь покупателя и проверить, что всё трекается

Что проверяем после каждого деплоя

  • Все ли рекомендуемые события присутствуют в DebugView
  • Нет ли задвоений (считаем количество purchase на 100 сессий)
  • Не изменилась ли структура dataLayer после обновления фронтенда

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

Компонент Описание
Аудит текущих тегов Проверка существующих GTM-тегов, dataLayer, дублей и ошибок
Дизайн событийной схемы Документация: список событий, параметры, триггеры
Настройка GA4 + GTM Создание конфигурации, тегов, Custom definitions
Яндекс.Метрика Установка счётчика, создание целей, настройка Вебвизора
Amplitude (опционально) Настройка клиентского и серверного SDK, когорты
QA и мониторинг Тестирование в Preview Mode, Alerting
Обучение и передача Доступы, инструкция по добавлению новых событий, консоль

Процесс и сроки

  1. Аудит текущих тегов и данных (2 дня)
  2. Дизайн событийной схемы (2 дня)
  3. Разработка Data Layer и настройка тегов (3–5 дней)
  4. QA в Preview Mode и на staging (2 дня)
  5. Деплой и настройка дашбордов (1 день)
Сценарий Срок
Базовая настройка GA4 + GTM 1 неделя
Полный e-commerce tracking + Метрика 2–3 недели
Server-side GTM + Amplitude 3–5 недель

Стоимость рассчитывается индивидуально. Получите консультацию по настройке веб-аналитики для вашего проекта — мы оценим объём работ за один день. Свяжитесь с нами, чтобы начать.

Wikipedia: Веб-аналитика — подробнее о методах и метриках. Официальная документация по событийной модели GA4 доступна в Google Analytics 4.