Reactive, responsive, beautiful charts for Angular based on Chart.js
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
The ThemeService provides dynamic theming capabilities for ng2-charts, allowing applications to change chart colors and styling at runtime based on user preferences or application state.
Injectable service that manages dynamic theme options for all charts in the application.
/**
* Service for managing dynamic chart themes
* Allows runtime updates to chart styling and colors
*/
@Injectable({
providedIn: 'root'
})
export class ThemeService {
/**
* Observable stream of current theme options
* Charts automatically update when this changes
*/
colorschemesOptions: BehaviorSubject<ChartOptions | undefined>;
}Set theme options that will be applied to all charts.
/**
* Sets global theme options for all charts
* Options are merged with individual chart options
* @param options - Chart.js options object with theme overrides
*/
setColorschemesOptions(options: ChartConfiguration['options']): void;
/**
* Gets the current theme options
* @returns Current theme configuration or undefined
*/
getColorschemesOptions(): ChartConfiguration['options'];Usage Examples:
import { Component, OnInit } from '@angular/core';
import { BaseChartDirective, ThemeService } from 'ng2-charts';
import { ChartOptions, ChartData } from 'chart.js';
@Component({
selector: 'app-theme-demo',
template: `
<div class="theme-controls">
<button (click)="setLightTheme()">Light Theme</button>
<button (click)="setDarkTheme()">Dark Theme</button>
<button (click)="setCustomTheme()">Custom Theme</button>
</div>
<canvas baseChart [data]="chartData" [type]="'bar'"></canvas>
`,
standalone: true,
imports: [BaseChartDirective]
})
export class ThemeDemoComponent implements OnInit {
constructor(private themeService: ThemeService) {}
chartData: ChartData = {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: 'Sample Data',
data: [12, 19, 3, 5, 2, 3]
}]
};
setLightTheme() {
const lightTheme: ChartOptions = {
plugins: {
legend: {
labels: {
color: '#333333'
}
}
},
scales: {
x: {
ticks: {
color: '#333333'
},
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
},
y: {
ticks: {
color: '#333333'
},
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
}
}
};
this.themeService.setColorschemesOptions(lightTheme);
}
setDarkTheme() {
const darkTheme: ChartOptions = {
plugins: {
legend: {
labels: {
color: '#ffffff'
}
}
},
scales: {
x: {
ticks: {
color: '#ffffff'
},
grid: {
color: 'rgba(255, 255, 255, 0.1)'
}
},
y: {
ticks: {
color: '#ffffff'
},
grid: {
color: 'rgba(255, 255, 255, 0.1)'
}
}
}
};
this.themeService.setColorschemesOptions(darkTheme);
}
setCustomTheme() {
const customTheme: ChartOptions = {
plugins: {
legend: {
labels: {
color: '#ff6b6b',
font: {
size: 14,
weight: 'bold'
}
}
}
},
scales: {
x: {
ticks: {
color: '#4ecdc4'
},
grid: {
color: 'rgba(78, 205, 196, 0.3)'
}
},
y: {
ticks: {
color: '#4ecdc4'
},
grid: {
color: 'rgba(78, 205, 196, 0.3)'
}
}
}
};
this.themeService.setColorschemesOptions(customTheme);
}
}The ThemeService uses a special merging behavior for theme options:
// Theme setting that affects ALL axes
const themeOptions: ChartOptions = {
scales: {
x: [{ // Single object template
ticks: { color: 'white' },
gridLines: { color: 'rgba(255,255,255,0.1)' }
}],
y: [{ // Single object template
ticks: { color: 'white' },
gridLines: { color: 'rgba(255,255,255,0.1)' }
}]
}
};
// This will apply the styling to ALL x and y axes in ALL charts
themeService.setColorschemesOptions(themeOptions);import { Component } from '@angular/core';
import { BaseChartDirective, ThemeService } from 'ng2-charts';
import { ChartOptions } from 'chart.js';
import { BehaviorSubject } from 'rxjs';
type Theme = 'light' | 'dark' | 'blue';
@Component({
selector: 'app-reactive-theme',
template: `
<select (change)="onThemeChange($event)">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="blue">Blue</option>
</select>
`,
standalone: true,
imports: [BaseChartDirective]
})
export class ReactiveThemeComponent {
private themeSubject = new BehaviorSubject<Theme>('light');
constructor(private themeService: ThemeService) {
this.themeSubject.subscribe(theme => {
this.applyTheme(theme);
});
}
onThemeChange(event: any) {
this.themeSubject.next(event.target.value as Theme);
}
private applyTheme(theme: Theme) {
const themes = {
light: {
plugins: {
legend: { labels: { color: '#333' } }
},
scales: {
x: { ticks: { color: '#333' } },
y: { ticks: { color: '#333' } }
}
},
dark: {
plugins: {
legend: { labels: { color: '#fff' } }
},
scales: {
x: { ticks: { color: '#fff' } },
y: { ticks: { color: '#fff' } }
}
},
blue: {
plugins: {
legend: { labels: { color: '#2196f3' } }
},
scales: {
x: { ticks: { color: '#2196f3' } },
y: { ticks: { color: '#2196f3' } }
}
}
};
this.themeService.setColorschemesOptions(themes[theme]);
}
}import { Injectable } from '@angular/core';
import { ThemeService } from 'ng2-charts';
import { ChartOptions } from 'chart.js';
@Injectable({
providedIn: 'root'
})
export class PersistentThemeService {
private readonly THEME_KEY = 'chart-theme';
constructor(private themeService: ThemeService) {
this.loadSavedTheme();
}
setTheme(options: ChartOptions) {
this.themeService.setColorschemesOptions(options);
localStorage.setItem(this.THEME_KEY, JSON.stringify(options));
}
private loadSavedTheme() {
const saved = localStorage.getItem(this.THEME_KEY);
if (saved) {
try {
const theme = JSON.parse(saved);
this.themeService.setColorschemesOptions(theme);
} catch (e) {
console.warn('Failed to load saved theme:', e);
}
}
}
}import { Component, OnInit } from '@angular/core';
import { BaseChartDirective, ThemeService } from 'ng2-charts';
import { ChartOptions, ChartData } from 'chart.js';
@Component({
selector: 'app-css-theme',
template: `<canvas baseChart [data]="chartData" [type]="'line'"></canvas>`,
styles: [`
:host {
--primary-color: #2196f3;
--text-color: #333333;
--grid-color: rgba(0, 0, 0, 0.1);
}
:host.dark-theme {
--primary-color: #90caf9;
--text-color: #ffffff;
--grid-color: rgba(255, 255, 255, 0.1);
}
`],
standalone: true,
imports: [BaseChartDirective]
})
export class CssThemeComponent implements OnInit {
chartData: ChartData = {
labels: ['January', 'February', 'March', 'April', 'May', 'June'],
datasets: [{
label: 'Sample Data',
data: [12, 19, 3, 5, 2, 3]
}]
};
constructor(private themeService: ThemeService) {}
ngOnInit() {
this.updateThemeFromCSS();
}
private updateThemeFromCSS() {
const computedStyle = getComputedStyle(document.documentElement);
const primaryColor = computedStyle.getPropertyValue('--primary-color').trim();
const textColor = computedStyle.getPropertyValue('--text-color').trim();
const gridColor = computedStyle.getPropertyValue('--grid-color').trim();
const theme: ChartOptions = {
plugins: {
legend: {
labels: {
color: textColor
}
}
},
scales: {
x: {
ticks: { color: textColor },
grid: { color: gridColor }
},
y: {
ticks: { color: textColor },
grid: { color: gridColor }
}
}
};
this.themeService.setColorschemesOptions(theme);
}
}interface ThemeOptions extends ChartOptions {
plugins?: {
legend?: {
labels?: {
color?: string;
font?: {
size?: number;
weight?: string | number;
family?: string;
};
};
};
tooltip?: {
backgroundColor?: string;
titleColor?: string;
bodyColor?: string;
borderColor?: string;
};
};
scales?: {
[key: string]: {
ticks?: {
color?: string;
font?: {
size?: number;
family?: string;
};
};
grid?: {
color?: string;
borderColor?: string;
};
};
};
}