This demo uses imperative chart updates. The generated code is a faithful Angular translation: open it in CodeSandbox to run and tweak.
import { Component, AfterViewInit, OnDestroy, ViewChild } from '@angular/core';
import {
ChartComponent,
ApexAxisChartSeries,
ApexNonAxisChartSeries,
ApexChart,
ApexXAxis,
ApexYAxis,
ApexTitleSubtitle,
ApexDataLabels,
ApexStroke,
ApexFill,
ApexLegend,
ApexTooltip,
ApexMarkers,
ApexPlotOptions,
ApexResponsive,
ApexGrid,
ApexAnnotations,
ApexStates,
ApexTheme,
NgApexchartsModule,
} from 'ng-apexcharts';
export type ChartOptions = {
series?: ApexAxisChartSeries | ApexNonAxisChartSeries;
chart?: ApexChart;
xaxis?: ApexXAxis;
yaxis?: ApexYAxis | ApexYAxis[];
title?: ApexTitleSubtitle;
subtitle?: ApexTitleSubtitle;
dataLabels?: ApexDataLabels;
stroke?: ApexStroke;
fill?: ApexFill;
legend?: ApexLegend;
tooltip?: ApexTooltip;
markers?: ApexMarkers;
plotOptions?: ApexPlotOptions;
responsive?: ApexResponsive[];
grid?: ApexGrid;
annotations?: ApexAnnotations;
states?: ApexStates;
theme?: ApexTheme;
colors?: string[];
labels?: any;
};
@Component({
selector: 'app-chart',
standalone: true,
imports: [NgApexchartsModule],
templateUrl: './chart.component.html',
})
export class AppChart implements AfterViewInit, OnDestroy {
@ViewChild('chart') chart!: ChartComponent;
private TAXONOMY: any = [
{
sector: 'Information Technology',
weight: 30,
drift: 0.9,
industries: [
{ name: 'Semiconductors', n: 14, weight: 12 },
{ name: 'Software', n: 16, weight: 11 },
{ name: 'Hardware & Devices', n: 9, weight: 7 },
],
},
{
sector: 'Financials',
weight: 15,
drift: -0.3,
industries: [
{ name: 'Banks', n: 15, weight: 6 },
{ name: 'Insurance', n: 11, weight: 4 },
{ name: 'Capital Markets', n: 10, weight: 5 },
],
},
{
sector: 'Health Care',
weight: 14,
drift: 0.2,
industries: [
{ name: 'Pharmaceuticals', n: 12, weight: 6 },
{ name: 'Biotechnology', n: 14, weight: 4 },
{ name: 'Medical Devices', n: 10, weight: 4 },
],
},
{
sector: 'Consumer Discretionary',
weight: 11,
drift: -0.6,
industries: [
{ name: 'Retail', n: 13, weight: 5 },
{ name: 'Automobiles', n: 8, weight: 4 },
{ name: 'Hotels & Leisure', n: 9, weight: 2 },
],
},
{
sector: 'Communication Services',
weight: 9,
drift: 1.4,
industries: [
{ name: 'Interactive Media', n: 9, weight: 5 },
{ name: 'Telecom', n: 8, weight: 2 },
{ name: 'Entertainment', n: 9, weight: 2 },
],
},
{
sector: 'Industrials',
weight: 8,
drift: 0.1,
industries: [
{ name: 'Aerospace & Defence', n: 8, weight: 3 },
{ name: 'Machinery', n: 10, weight: 3 },
{ name: 'Transport', n: 9, weight: 2 },
],
},
{
sector: 'Consumer Staples',
weight: 6,
drift: 0.4,
industries: [
{ name: 'Food & Beverage', n: 11, weight: 3 },
{ name: 'Household Products', n: 8, weight: 3 },
],
},
{
sector: 'Energy',
weight: 5,
drift: -1.7,
industries: [
{ name: 'Oil & Gas', n: 12, weight: 4 },
{ name: 'Renewables', n: 8, weight: 1 },
],
},
{
sector: 'Utilities',
weight: 4,
drift: -0.2,
industries: [
{ name: 'Electric Utilities', n: 9, weight: 3 },
{ name: 'Water & Gas', n: 6, weight: 1 },
],
},
{
sector: 'Real Estate',
weight: 4,
drift: -0.9,
industries: [
{ name: 'REITs', n: 11, weight: 3 },
{ name: 'Property Services', n: 6, weight: 1 },
],
},
{
sector: 'Materials',
weight: 4,
drift: 0.6,
industries: [
{ name: 'Chemicals', n: 9, weight: 2 },
{ name: 'Metals & Mining', n: 9, weight: 2 },
],
},
];
private rng = (seed: any): any => {
var s = seed >>> 0
return function () {
s = (s * 1664525 + 1013904223) >>> 0
return s / 4294967296
}
};
private gauss = (u: any): any => {
var g = 0
for (var k = 0; k < 6; k++) g += u()
return (g - 3) / Math.sqrt(0.5)
};
private LETTERS: any = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
private ticker = (u: any): any => {
var out = ''
var len = 3 + (u() > 0.65 ? 1 : 0)
for (var i = 0; i < len; i++) {
out += this.LETTERS.charAt(Math.floor(u() * 26))
}
return out
};
private seen: any = {};
private u: any = this.rng(20260812);
private marketSeries: any = [
{
name: 'Market',
data: this.TAXONOMY.map(function (sec) {
return {
x: sec.sector,
children: sec.industries.map(function (ind) {
var companies = []
for (var i = 0; i < ind.n; i++) {
// Heavy tail: a few names carry most of the industry's value.
var scale = Math.exp(this.gauss(this.u) * 0.85)
var cap = Math.max(4, Math.round((ind.weight * 90 * scale) / ind.n))
// The day's move: the sector drifts, the name adds its own noise.
var change = Math.round((sec.drift + this.gauss(this.u) * 1.6) * 100) / 100
var t = this.ticker(this.u)
while (this.seen[t]) t = this.ticker(this.u)
this.seen[t] = true
companies.push({ x: t, y: cap, colorValue: change })
}
return { x: ind.name, children: companies }
}),
}
}),
},
];
private fmtCap = (v: any): any => {
return v >= 1000 ? '$' + (v / 1000).toFixed(2) + 'T' : '$' + v + 'B'
};
private fmtPct = (v: any): any => {
return (v > 0 ? '+' : '') + v.toFixed(2) + '%'
};
public chartOptions: Partial<ChartOptions> = {
series: this.marketSeries,
chart: {
id: 'marketMap',
type: 'treemap',
height: 620,
fontFamily: 'inherit',
background: 'transparent',
animations: {
enabled: true,
speed: 500,
},
toolbar: {
show: false,
},
},
theme: {
mode: 'dark',
},
title: {
// Kept to one line: the breadcrumb that appears once you zoom in sits in the
// band between the title and the plot, and a subtitle would squeeze it out.
text: "Market map by sector - area is market value, colour is the day's move",
align: 'left',
style: {
fontSize: '14px',
fontWeight: 600,
},
},
legend: {
show: true,
position: 'bottom',
},
stroke: {
width: 1,
colors: ['#0f1115'],
},
dataLabels: {
enabled: true,
style: {
fontSize: '11px',
fontWeight: 600,
},
},
tooltip: {
enabled: true,
custom: ({ seriesIndex, dataPointIndex, w }) => {
var d = w.config.series[seriesIndex].data[dataPointIndex]
var up = d.colorValue >= 0
return (
'<div class="apexcharts-tooltip-title" style="font-weight:600">' +
d.x +
'</div>' +
'<div style="padding:6px 10px 8px">' +
this.fmtCap(d.y) +
' <span style="color:' +
(up ? '#3ddc84' : '#ff6b5e') +
'">' +
this.fmtPct(d.colorValue) +
'</span></div>'
)
},
},
plotOptions: {
treemap: {
borderRadius: 2,
dataLabels: {
format: 'truncate',
},
colorScale: {
// Colour by the day's move, not by market value.
colorValue: 'colorValue',
gradient: {
colors: ['#c0392b', '#5b6570', '#1e9e57'],
midpoint: 0,
},
gradientLegend: {
enabled: true,
width: '46%',
thickness: 10,
formatter: (v) => {
return (v > 0 ? '+' : '') + v.toFixed(1) + '%'
},
},
},
// Click a sector or an industry to fill the canvas with it.
zoom: {
enabled: true,
},
parents: {
padding: 3,
tooltip: {
formatter: (o) => {
return (
'<div class="apexcharts-tooltip-title" style="font-weight:600">' +
o.name +
'</div>' +
'<div style="padding:6px 10px 8px">' +
this.fmtCap(o.value) +
' · ' +
o.leafCount +
' companies · ' +
o.percentOfTotal.toFixed(1) +
'% of market</div>'
)
},
},
},
levels: [
{
// Sector.
padding: 5,
header: {
height: 26,
minWidth: 64,
style: {
fontSize: '13px',
fontWeight: 700,
},
formatter: (name, o) => {
return name + ' ' + this.fmtCap(o.value)
},
},
},
{
// Industry.
padding: 2,
header: {
height: 15,
minWidth: 46,
style: {
fontSize: '10px',
fontWeight: 500,
},
},
},
],
},
},
};
ngAfterViewInit() {
(window as any).ApexCharts.setLicense('APEX-eyJleHBpcnlEYXRlIjoiMjEyNi0wNy0wNCIsImlzc3VlRGF0ZSI6IjIwMjYtMDctMjgiLCJwbGFuIjoicHJlbWl1bSIsImRvbWFpbnMiOlsiYXBleGNoYXJ0cy5jb20iLCIxMjcuMC4wLjEiLCJsb2NhbGhvc3QiXSwic2lnIjoieVBmb1VCc0Z3TU9ZdUEyaEZkR0I2Y1FtZ0JITUtXcVdJSjB2NVRESXRZbFR3eDJMUmh6R2x0RUc3VXJ4X0s3b25ZMWRZb2Z2VGItN01ydFYyNDVyOWcifQ==');
}
ngOnDestroy() {
// no cleanup needed
}
}