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

TreeMap — это визуализация, где площадь прямоугольника = вес, а цвет = метрика. Отлично подходит для market heatmap (карта рынка), портфелей, иерархий ресурсов и любых “взвешенных деревьев”.
Мне TreeMap понадобился в Angular-проекте под “тепловые карты” и разные иерархические отчёты. Казалось бы — задача стандартная, значит решение должно быть “в один npm install”. Но реальность оказалась неожиданной: готовых TreeMap-решений именно для Angular практически нет.
В итоге я сделал свой standalone компонент и оформил его в npm-пакет: stockchart-treemap.
Дисклеймер
Пакет бесплатный.
Это не реклама — мне никто не заплатил ни копейки.
Я делал компонент в первую очередь для своих задач, а потом упаковал так, чтобы можно было переиспользовать и не зависеть от внешних UI-комбайнов.
Почему не использовать готовое решение
Вот тут самое забавное: обычно в статьях пишут “не подошло потому что тяжелое/дорогое/не то API”. У меня было проще — мне просто не удалось найти нормальный готовый TreeMap под Angular.
Что я видел в процессе поисков:
Почти всё — под чистый JS, React или Vue
TreeMap как визуализация часто встречается в “JS-мире”: примеры на D3-подобных штуках, готовые виджеты, демки. Но это не Angular-компонент, а код, который сам рисует DOM/SVG/Canvas.
В Angular это превращается в вечную обвязку:
ElementRef,ngAfterViewInit, ручной lifecycle,чистка при destroy,
ручные обновления при изменении данных,
танцы с change detection,
и “почему оно перерисовалось два раза/не там/не тогда”.
То есть формально “решение есть”, но по ощущениям это уже не “встроил компонент”, а “интегрировал чужой движок”.
То, что называется “Angular TreeMap”, часто:
устаревшее (старые версии Angular, нет standalone),
или это тонкая обёртка над JS-ядром без нормального Angular UX (шаблоны, события, типизация, обновления данных — всё “на честном слове”).
Есть варианты в составе больших 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 / colorFieldcolorValueField— поле для расчётаcolorScalecolors— палитра (если пусто — можно полагаться наcolorScaleили fallback)titleSize,showTopLevelTitlesderiveParentValueFromChildren— если у контейнера нет value, можно вывести из детейroundDecimals— помогает стабилизировать координатыminTileSize— минимальный размер плитки для рекурсии
Немного про “как я делал”
Я сознательно старался избежать “магии”. Внутри всё раскладывается по шагам:
Нормализация данных (любой источник → массив нод).
Подсчёт значений (в т.ч. вычисление контейнеров из детей — опционально).
Layout (squarified / slice-and-dice) рекурсивно разбивает прямоугольник.
Цвет (палитра или интерполяция по шкале).
Рендер + шаблоны + события.
На практике это даёт главное: предсказуемость и возможность легко встроить компонент в проект, не подстраиваясь под чужие решения.
Где это полезно
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
nizami-dev
Хотя бы одну картинку добавили бы
youscriptor Автор
побоялся, у меня аккаунт снесли когда то этого делал. Надеюсь в этот раз публикацию не удалят. Добавил