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 DAY: any = 86400000;
private calNow: any = new Date();
private calEnd: any = Date.UTC(
this.calNow.getUTCFullYear(),
this.calNow.getUTCMonth(),
this.calNow.getUTCDate(),
);
private calStart: any = this.calEnd - 364 * this.DAY;
private weekStartOf = (ms: any): any => {
return ms - new Date(ms).getUTCDay() * this.DAY
};
private buildCalendar = (): any => {
var weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
var byDay = weekdays.map(function (n) {
return { name: n, data: [] }
})
// Deterministic PRNG so the demo looks the same on every reload.
var seed = 20240407
function rand() {
seed = (seed * 1103515245 + 12345) & 0x7fffffff
return seed / 0x7fffffff
}
var totalWeeks = Math.round(
(this.weekStartOf(this.calEnd) - this.weekStartOf(this.calStart)) / (7 * this.DAY),
)
for (var t = this.calStart; t <= this.calEnd; t += this.DAY) {
var dow = new Date(t).getUTCDay()
var wk = Math.round((this.weekStartOf(t) - this.weekStartOf(this.calStart)) / (7 * this.DAY))
// A gentle seasonal wave so some months look busier than others.
var season = 0.5 + 0.5 * Math.sin((wk / totalWeeks) * Math.PI * 2 - 1)
var count = 0
// ~55% of days have activity; weekends are lighter.
if (rand() < 0.35 + 0.4 * season) {
var bias = dow === 0 || dow === 6 ? 0.5 : 1
// rand()*rand() skews toward the low buckets, like real activity.
count = Math.round(rand() * rand() * 16 * bias) + 1
}
// x is the week's Sunday (so the column lines up); the cell's true date is
// stashed on the datum for the tooltip (x can't carry it: all 7 weekdays in
// a column share the same x).
byDay[dow].data.push({ x: this.weekStartOf(t), y: count, date: t })
}
// Rows read Sun (top) -> Sat (bottom). ApexCharts draws the last series on
// top, so reverse the weekday order before returning.
return byDay.reverse()
};
private calendarData: any = this.buildCalendar();
private calMinX: any = this.weekStartOf(this.calStart) - 3.5 * this.DAY;
private calMaxX: any = this.weekStartOf(this.calEnd) + 3.5 * this.DAY;
public chartOptions: Partial<ChartOptions> = {
series: this.calendarData,
chart: {
height: 186,
width: '100%',
type: 'heatmap',
toolbar: { show: false },
animations: { enabled: false },
},
title: {
text: 'Contribution activity',
align: 'center',
style: { fontSize: '14px', fontWeight: 600 },
},
dataLabels: { enabled: false },
// A small light gap between cells, like a contributions calendar.
stroke: { width: 3, colors: ['#fff'] },
legend: { show: false },
states: {
active: {
filter: {
type: 'none',
},
},
},
plotOptions: {
heatmap: {
radius: 2,
// Flat bucket colors (no within-range shading), so each level is one color.
enableShades: false,
colorScale: {
ranges: [
{ from: 0, to: 0, name: '0', color: '#ebedf0' },
{ from: 1, to: 3, name: '1-3', color: '#9be9a8' },
{ from: 4, to: 7, name: '4-7', color: '#40c463' },
{ from: 8, to: 11, name: '8-11', color: '#30a14e' },
{ from: 12, to: 100, name: '12+', color: '#216e39' },
],
},
},
},
xaxis: {
type: 'datetime',
min: this.calMinX,
max: this.calMaxX,
position: 'top',
labels: {
format: 'MMM',
datetimeUTC: false,
style: { colors: '#767676', fontSize: '12px' },
},
axisBorder: { show: false },
axisTicks: { show: false },
tooltip: { enabled: false },
crosshairs: {
show: false,
},
},
yaxis: {
// Show only alternate weekday labels (Mon / Wed / Fri), like the original.
labels: {
formatter: (val) => {
return ['Mon', 'Wed', 'Fri'].indexOf(val) >= 0 ? val : ''
},
style: { colors: ['#767676'], fontSize: '12px' },
},
},
grid: {
yaxis: {
lines: {
show: false,
},
},
},
tooltip: {
// Custom tooltip so it can show the cell's real date (stashed on the datum)
// plus the count, e.g. "5 contributions on Monday, January 8, 2024".
custom: (opts) => {
var pt = opts.w.config.series[opts.seriesIndex].data[opts.dataPointIndex]
var n = pt.y
var when = new Date(pt.date).toLocaleDateString('en-US', {
dateStyle: 'medium',
timeZone: 'UTC',
})
var count =
n === 0
? 'No contributions'
: n + (n === 1 ? ' contribution' : ' contributions')
return (
'<div style="padding:6px 10px;font-size:13px">' +
'<b>' +
count +
'</b> on ' +
when +
'</div>'
)
},
},
};
ngAfterViewInit() {
(window as any).ApexCharts.setLicense('APEX-eyJpc3N1ZURhdGUiOiIyMDI2LTA0LTE2IiwiZXhwaXJ5RGF0ZSI6IjIwNTItMDQtMTciLCJwbGFuIjoiZW50ZXJwcmlzZSIsImRvbWFpbnMiOlsiYXBleGNoYXJ0cy5jb20iLCIxMjcuMC4wLjEiXX0=');
}
ngOnDestroy() {
// no cleanup needed
}
}