Visualizing hierarchical data can be challenging when you need to display large amounts of information in a limited space. A treemap chart represents hierarchical data using nested rectangles. Each rectangle represents a data item, and its size is proportional to a numerical value.
Treemaps are useful for financial portfolios, storage usage, sales distribution, analytics dashboards, and resource allocation. In this tutorial, we’ll create a responsive treemap chart in Angular using D3.js.
What is a Treemap Chart?
A treemap displays hierarchical data as nested rectangles. The area of each rectangle corresponds to the value associated with that item, while colors help distinguish groups.
1. Create an Angular Project
If you already have an Angular application, use your existing project. Otherwise, create a new one:
ng new angular-d3-treemapcd angular-d3-treemap
Install D3.js and its TypeScript definitions:
npm install d3npm install -D @types/d3
2. Define the Hierarchical Data
D3 treemaps work with nested data. Parent nodes contain children, and leaf nodes contain numerical values.
export interface TreemapNode { name: string; value?: number; children?: TreemapNode[];}export const TREEMAP_DATA: TreemapNode = { name: 'Company', children: [ { name: 'Engineering', children: [ { name: 'Frontend', value: 40 }, { name: 'Backend', value: 60 }, { name: 'Infrastructure', value: 30 } ] }, { name: 'Marketing', children: [ { name: 'Digital', value: 35 }, { name: 'Content', value: 20 }, { name: 'Events', value: 15 } ] }, { name: 'Sales', children: [ { name: 'Enterprise', value: 50 }, { name: 'SMB', value: 25 } ] } ]};
Here, Company is the root node, departments are parent nodes, and teams are leaf nodes with numerical values.
3. Create the Angular Treemap Component
Generate a component:
ng generate component treemap-chart
In treemap-chart.component.ts, use D3 to calculate and render the chart. The component uses ResizeObserver to redraw the chart when its container changes size.
import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild} from '@angular/core';import * as d3 from 'd3';interface TreemapNode { name: string; value?: number; children?: TreemapNode[];}@Component({ selector: 'app-treemap-chart', standalone: true, templateUrl: './treemap-chart.component.html', styleUrl: './treemap-chart.component.css'})export class TreemapChartComponent implements AfterViewInit, OnDestroy { @ViewChild('chartContainer', { static: true }) chartContainer!: ElementRef; private resizeObserver?: ResizeObserver; private readonly data: TreemapNode = { name: 'Company', children: [ { name: 'Engineering', children: [ { name: 'Frontend', value: 40 }, { name: 'Backend', value: 60 }, { name: 'Infrastructure', value: 30 } ]}, { name: 'Marketing', children: [ { name: 'Digital', value: 35 }, { name: 'Content', value: 20 }, { name: 'Events', value: 15 } ]}, { name: 'Sales', children: [ { name: 'Enterprise', value: 50 }, { name: 'SMB', value: 25 } ]} ] }; ngAfterViewInit(): void { this.renderTreemap(); this.resizeObserver = new ResizeObserver(() => this.renderTreemap()); this.resizeObserver.observe(this.chartContainer.nativeElement); } private renderTreemap(): void { const container = this.chartContainer.nativeElement; const width = container.clientWidth; const height = 420; if (width <= 0) return; const host = d3.select(container); host.selectAll('svg').remove(); const root = d3.hierarchy(this.data) .sum(node => node.value ?? 0) .sort((a, b) => (b.value ?? 0) - (a.value ?? 0)); const layout = d3.treemap() .size([width, height]) .paddingOuter(4) .paddingInner(3) .paddingTop(22) .round(true)(root); const color = d3.scaleOrdinal() .domain(['Engineering', 'Marketing', 'Sales']) .range(['#4e79a7', '#f28e2b', '#59a14f']); const svg = host.append('svg') .attr('viewBox', `0 0 ${width} ${height}`) .attr('width', '100%') .attr('height', height) .attr('role', 'img') .attr('aria-label', 'Treemap showing company budget allocation by department'); const departments = svg .selectAll<SVGGElement, d3.HierarchyRectangularNode>('g.department') .data(layout.children ?? []) .join('g') .attr('class', 'department'); departments.append('rect') .attr('x', d => d.x0) .attr('y', d => d.y0) .attr('width', d => Math.max(0, d.x1 - d.x0)) .attr('height', d => Math.max(0, d.y1 - d.y0)) .attr('fill', d => color(d.data.name)) .attr('rx', 4); departments.append('text') .attr('x', d => d.x0 + 8) .attr('y', d => d.y0 + 16) .attr('fill', '#fff') .attr('font-size', 12) .attr('font-weight', '600') .text(d => d.data.name); const teams = departments .selectAll<SVGGElement, d3.HierarchyRectangularNode>('g.team') .data(d => d.leaves()) .join('g') .attr('class', 'team'); teams.append('rect') .attr('x', d => d.x0) .attr('y', d => d.y0) .attr('width', d => Math.max(0, d.x1 - d.x0)) .attr('height', d => Math.max(0, d.y1 - d.y0)) .attr('fill', d => color(d.parent?.data.name ?? '')) .attr('fill-opacity', 0.88) .attr('stroke', '#fff') .attr('stroke-width', 1) .attr('rx', 2); teams.append('text') .attr('x', d => d.x0 + 6) .attr('y', d => d.y0 + 17) .attr('fill', '#fff') .attr('font-size', 11) .attr('pointer-events', 'none') .text(d => d.data.name) .each(function(d) { const label = d3.select(this); if (d.x1 - d.x0 - 12 < 45 || d.y1 - d.y0 13) { label.text(d.data.name.slice(0, 12) + '…'); } }); teams.append('text') .attr('x', d => d.x0 + 6) .attr('y', d => d.y0 + 33) .attr('fill', '#fff') .attr('font-size', 10) .attr('pointer-events', 'none') .text(d => d.data.value ?? 0) .each(function(d) { if (d.y1 - d.y0 < 40) d3.select(this).remove(); }); } ngOnDestroy(): void { this.resizeObserver?.disconnect(); }}
How it works
d3.hierarchy()converts the nested object into a hierarchy..sum()calculates aggregated values for parent nodes.d3.treemap()computes rectangle coordinates:x0,y0,x1, andy1.- An ordinal color scale assigns consistent colors to departments and their teams.
ResizeObserverredraws the chart when the container changes size and is disconnected when the component is destroyed.
4. Create the Component Template
In treemap-chart.component.html:
<div class="treemap-wrapper"> <h2>Company Budget Allocation</h2> <p class="chart-description"> Explore the relative budget allocation across departments and teams. </p> <div #chartContainer class="treemap-container"></div></div>
5. Add Component Styles
In treemap-chart.component.css:
:host { display: block; width: 100%;}.treemap-wrapper { width: 100%; padding: 16px; box-sizing: border-box; font-family: Arial, sans-serif;}.treemap-wrapper h2 { margin: 0 0 8px; font-size: 22px; color: #222;}.chart-description { margin: 0 0 16px; color: #666; font-size: 14px; line-height: 1.5;}.treemap-container { width: 100%; min-height: 200px; overflow: hidden;}.treemap-container svg { display: block; max-width: 100%;}.treemap-container rect { transition: fill-opacity 150ms ease;}.treemap-container .team:hover rect { fill-opacity: 1; stroke: #222; stroke-width: 1.5;}
6. Use the Component in Your Angular Application
For a standalone Angular application, import the chart component into your root component. For example, in app.component.ts:
import { Component } from '@angular/core';import { TreemapChartComponent } from './treemap-chart/treemap-chart.component';@Component({ selector: 'app-root', standalone: true, imports: [TreemapChartComponent], template: '<main><app-treemap-chart></app-treemap-chart></main>'})export class AppComponent {}
Adjust the import path to match your project, then start the development server:
ng serve
Open the local URL shown by Angular CLI, usually http://localhost:4200.
7. Customize the Treemap
Change the chart height
const height = 500;
You can also calculate height from the container width to maintain an aspect ratio.
Change the color palette
const color = d3.scaleOrdinal() .domain(['Engineering', 'Marketing', 'Sales']) .range(['#6a51a3', '#238b45', '#cb181d']);
Choose colors with enough contrast to keep labels readable.
Adjust rectangle spacing
d3.treemap() .size([width, height]) .paddingOuter(6) .paddingInner(4) .paddingTop(26);
paddingOuter()controls spacing around the overall layout.paddingInner()controls spacing between sibling rectangles.paddingTop()reserves room for parent labels.
Try a different tiling strategy
d3.treemap() .tile(d3.treemapBinary) .size([width, height]);
D3 also provides d3.treemapSquarify, d3.treemapResquarify, and d3.treemapSliceDice. Each strategy arranges the rectangles differently.
8. Common Issues and Troubleshooting
| Issue | What to check |
|---|---|
| Chart is not displayed | Ensure the view has initialized and the container width is greater than zero. |
| TypeScript errors for coordinates | Use the rectangular hierarchy returned by d3.treemap(). |
| Labels overlap | Reduce font sizes or hide labels in smaller rectangles. |
| Incorrect rectangle sizes | Check that leaf values are valid, non-negative numbers. |
| Chart does not resize | Verify that ResizeObserver observes the correct container. |
| Repeated SVG elements | Remove the previous SVG before redrawing. |
For production applications, handle empty data and zero-value datasets, validate incoming API data, and provide an accessible text alternative to the visualization.
9. When Should You Use a Treemap?
- Financial dashboards: Show portfolio allocation by sector and asset.
- Storage monitoring: Display disk usage by folder and file category.
- Sales analytics: Compare revenue across regions, products, and teams.
- Business intelligence: Visualize budgets, expenses, and resource distribution.
- Application monitoring: Compare resource consumption across services.
A treemap is less suitable when readers need to compare very similar values precisely. A bar chart may make those differences easier to judge.
Conclusion
In this tutorial, we created a treemap chart in Angular using D3.js. We covered hierarchical data, layout calculation, SVG rendering, group-based colors, labels, and responsive resizing.
Angular and D3.js work well together when you need more control over a visualization than a predefined chart component offers. You can extend this example with tooltips, click events, API-driven data, drill-down navigation, and configurable color schemes.