Standalone TreeMap для Angular без лишних зависимостей: как я сделал stockchart-treemap

TreeMap — это визуализация, где площадь прямоугольника = вес, а цвет = метрика. Отлично подходит для market heatmap (карта рынка), портфелей, иерархий ресурсов и любых “взвешенных деревьев”.

Мне TreeMap понадобился в Angular-проекте под “тепловые карты” и разные иерархические отчёты. Казалось бы — задача стандартная, значит решение должно быть “в один npm install”. Но реальность оказалась неожиданной: готовых TreeMap-решений именно для Angular практически нет.

В итоге я сделал свой standalone компонент и оформил его в npm-пакет: stockchart-treemap.

Дисклеймер

  • Пакет бесплатный.

  • Это не реклама — мне никто не заплатил ни копейки.

  • Я делал компонент в первую очередь для своих задач, а потом упаковал так, чтобы можно было переиспользовать и не зависеть от внешних UI-комбайнов.

Почему не использовать готовое решение

Вот тут самое забавное: обычно в статьях пишут “не подошло потому что тяжелое/дорогое/не то API”. У меня было проще — мне просто не удалось найти нормальный готовый TreeMap под Angular.

Что я видел в процессе поисков:

  1. Почти всё — под чистый JS, React или Vue
    TreeMap как визуализация часто встречается в “JS-мире”: примеры на D3-подобных штуках, готовые виджеты, демки. Но это не Angular-компонент, а код, который сам рисует DOM/SVG/Canvas.

В Angular это превращается в вечную обвязку:

  • ElementRef, ngAfterViewInit, ручной lifecycle,

  • чистка при destroy,

  • ручные обновления при изменении данных,

  • танцы с change detection,

  • и “почему оно перерисовалось два раза/не там/не тогда”.

То есть формально “решение есть”, но по ощущениям это уже не “встроил компонент”, а “интегрировал чужой движок”.

  1. То, что называется “Angular TreeMap”, часто:

  • устаревшее (старые версии Angular, нет standalone),

  • или это тонкая обёртка над JS-ядром без нормального Angular UX (шаблоны, события, типизация, обновления данных — всё “на честном слове”).

  1. Есть варианты в составе больших UI-комбайнов
    Иногда это нормально, но лично мне не хотелось тащить “полплатформы” ради одного TreeMap. Особенно когда в проекте уже есть своя дизайн-система и свои компоненты.

Итог: формально решений много, но “поставил и получил нативный Angular-компонент с понятной интеграцией” — мне не попалось. Поэтому я решил сделать компонент так, как мне самому было бы удобно его использовать.

Что получилось: stockchart-treemap

stockchart-treemap — standalone TreeMap компонент для Angular:

  • без внешних runtime-зависимостей (никаких Kendo UI и т.п.),

  • с цветовой шкалой по значениям (min/center/max),

  • с кастомными шаблонами плиток и заголовков,

  • с несколькими режимами источника данных,

  • с событиями клика/ховера и “путём” до узла.

Демо

StackBlitz: https://stackblitz.com/edit/stackblitz-starters-drgync8z?file=README.md

Установка

npm install stockchart-treemap

Peer deps: @angular/core и @angular/common 20.x.

Быстрый старт

Пример “из коробки”: просто массив + настройки полей.

import { Component } from '@angular/core';
import { TreeMapComponent, TreeMapOptions } from 'stockchart-treemap';

@Component({
  standalone: true,
  selector: 'app-treemap-demo',
  imports: [TreeMapComponent],
  template: `
    <stockchart-treemap
      [data]="data"
      [options]="options">
    </stockchart-treemap>
  `
})
export class TreemapDemoComponent {
  data = [
    { name: 'Tech', value: 35, change: 4.2, items: [
      { name: 'A', value: 20, change: 5.1 },
      { name: 'B', value: 15, change: 2.0 }
    ]},
    { name: 'Energy', value: 25, change: -3.4 },
    { name: 'Health', value: 18, change: 1.6 }
  ];

  options: Partial<TreeMapOptions> = {
    textField: 'name',
    valueField: 'value',
    childrenField: 'items',

    // цвет — по "change"
    colorValueField: 'change',
    colorScale: { min: '#E53935', center: '#F5F5F5', max: '#2E7D32' },

    // оставляем пустым, чтобы полагаться на colorScale
    colors: []
  };
}

Источники данных: три режима, потому что в жизни по-разному

Визуализация — это всегда “последний метр” после данных. И данные могут приходить как угодно, поэтому я сделал три режима.

1) Обычный массив

Самый простой: [data]="data".

2) Observable

Если данные живые (RxJS-пайплайн, сокеты, периодические обновления на вашей стороне) — можно отдать data$, компонент подпишется и будет перерисовываться при новых значениях.

3) Loader-функция + polling

Иногда удобнее дать компоненту “как загрузить данные”, а он сам будет обновляться раз в N миллисекунд:

loader = () => this.http.get<Item[]>('/api/treemap');
refreshMs = 5000; // обновление каждые 5 секунд

Плюс есть refreshNow() — полезно для “обновить по кнопке” или после смены фильтров.

Цвета: палитра или шкала по значению

Мне нужны были оба сценария:

Палитра

Когда цвет — это категория (например, разные отделы/типы). Можно задать:

  • colors: ['#...', '#...', ...]

  • либо пары [min,max], чтобы генерировать градиенты по уровням.

Шкала по значению (heatmap)

Классика для “карты рынка”: у элемента есть метрика (например, change), и цвет вычисляется по шкале:

  • min — красный,

  • center — нейтральный,

  • max — зелёный.

Работает так:

  • задаём colorValueField,

  • задаём colorScale,

  • и получаем прогнозируемый градиент.

Если у данных вообще нет цветового поля — оттенки для вложенных уровней считаются автоматически, чтобы дерево было читабельным.

Layout-ы: squarified и slice-and-dice

Я сделал два варианта разметки:

  • squarified (по умолчанию) — стремится к “квадратности” плиток, обычно выглядит лучше.

  • horizontal / vertical — slice-and-dice, нарезка полосами с чередованием ориентации по уровням.

Это покрывает большую часть практических кейсов без превращения компонента в “комбайн настроек”.

Кастомные шаблоны: плитка и заголовок

TreeMap без нормальных шаблонов быстро превращается в “красивую мозаику”, из которой нельзя извлечь смысл.

Можно задать отдельно:

  • titleTemplate — заголовок контейнеров,

  • tileTemplate — содержимое плитки (обычно лист).

Пример:

<stockchart-treemap
  [data]="data"
  [options]="options"
  [tileTemplate]="tile"
  [titleTemplate]="title"
  (tileClick)="onTile($event)">
</stockchart-treemap>

<ng-template #title let-item let-node="node">
  <div class="title">
    {{ item.name }} · {{ node.value | number:'1.0-0' }}
  </div>
</ng-template>

<ng-template #tile let-item let-isLeaf="isLeaf">
  <div class="tile">
    <strong>{{ item.name }}</strong>
    <span *ngIf="isLeaf">{{ item.change }}%</span>
  </div>
</ng-template>

События и “путь” до узла

В интерактивных сценариях важно не только “что кликнули”, но и “где это в дереве”.

Поэтому в событиях есть:

  • dataItem — исходный объект,

  • path — цепочка элементов от корня до выбранной ноды.

import { TreeMapEvent } from 'stockchart-treemap';

onTile(ev: TreeMapEvent<MyNode>) {
  console.log(ev.dataItem);
  console.log(ev.path); // полезно для breadcrumbs / drilldown
}

Ключевые опции

То, что реально используется чаще всего:

  • textField / valueField / childrenField / colorField

  • colorValueField — поле для расчёта colorScale

  • colors — палитра (если пусто — можно полагаться на colorScale или fallback)

  • titleSize, showTopLevelTitles

  • deriveParentValueFromChildren — если у контейнера нет value, можно вывести из детей

  • roundDecimals — помогает стабилизировать координаты

  • minTileSize — минимальный размер плитки для рекурсии

Немного про “как я делал”

Я сознательно старался избежать “магии”. Внутри всё раскладывается по шагам:

  1. Нормализация данных (любой источник → массив нод).

  2. Подсчёт значений (в т.ч. вычисление контейнеров из детей — опционально).

  3. Layout (squarified / slice-and-dice) рекурсивно разбивает прямоугольник.

  4. Цвет (палитра или интерполяция по шкале).

  5. Рендер + шаблоны + события.

На практике это даёт главное: предсказуемость и возможность легко встроить компонент в проект, не подстраиваясь под чужие решения.

Где это полезно

  • Market heatmap: сектор → индустрия → компания (площадь = вес, цвет = % change).

  • Портфель: активы → позиции (площадь = доля, цвет = PnL).

  • Ресурсы: департаменты → проекты → сервисы (площадь = расходы/нагрузка, цвет = отклонение).

  • Любые метрики в дереве: storage usage, распределение задач, coverage, KPI и т.д.

Ограничения

Если попытаться отображать десятки тысяч нод одновременно — DOM начнёт страдать (как и у большинства UI-визуализаций). Этот компонент рассчитан на нормальные продуктовые сценарии, а не на экстремальные бенчмарки.

Если когда-нибудь понадобится “очень много элементов” — это уже территория Canvas/WebGL/виртуализации.

Заключение

stockchart-treemap появился из простой причины: TreeMap для Angular почему-то оказался редкостью, а интегрировать JS-виджеты “как есть” в Angular — это отдельный проект по обвязке.

Я сделал standalone компонент, который ставится из npm, нормально живёт в Angular, поддерживает разные источники данных, цветовые шкалы и кастомные шаблоны.

Демо: https://stackblitz.com/edit/stackblitz-starters-drgync8z?file=README.md
Пакет: npm install stockchart-treemap

Комментарии (3)


  1. nizami-dev
    09.01.2026 12:38

    Хотя бы одну картинку добавили бы


    1. youscriptor Автор
      09.01.2026 12:38

      побоялся, у меня аккаунт снесли когда то этого делал. Надеюсь в этот раз публикацию не удалят. Добавил


  1. awesomeday
    09.01.2026 12:38

    Е