head image CSS variables in Angular. Charts: Part 3

CSS variables in Angular: Part 3. Charts

Use CSS variables for Canvas and SVG charts

Maksim Dolgikh
5 min readDec 25, 2023

--

Previous parts

Chart styling as we know it

Styling charts is another area where you need to use colors. There are a lot of color palettes for charts on the internet, but not every color palette can fit our brandbook and we have to apply “our” colors to maintain the overall style.

Often, it is a regular preset with hardcoded colors. This approach completely covers most graph styling issues if only 1 interface color scheme is used.

But what about supporting more than one color scheme?

Switching themes does not affect charts
Switching themes does not affect charts

Adapting charts for each theme is a challenge for developers. And if experience is not enough, you can even meet such implementations

backgroundColor = theme === 'dark' ? 'white' : 'black'

It’s scary 😱 to imagine if detailed stylization of each type of chart would be required.

Let’s suppose developers have solved the problem of this approach and made a ColorsService that provides the desired color by colorName, hiding the actual value.

export class ColorsService {
private themeMap: Map<TThemes, Map<TColorName, string>> = new Map([
['default', new Map<TColorName, string>([
['primary', '#d75894'],
['secondary', '#9b3dca'],
['chart-color-1', '#526ed3'],
['chart-color-2', '#ea97c4'],
['chart-color-3', '#fee797'],
['base-1', '#808080'],
['base-2', '#bebebe'],
])],
['dark', new Map<TColorName, string>([
['primary', '#5e84ff'],
['secondary', '#0dd0ff'],
['chart-color-1', '#ffce56'],
['chart-color-2', '#36a2eb'],
['chart-color-3', '#ff6384'],
['base-1', '#c7c7c7'],
['base-2', '#bcbcbc'],
])],
])


private paletteMap: Map<TColorName, string> = this.themeMap.get('default');


constructor(
private themeSwitcher: ThemeSwitcherService,
) {
this.themeSwitcher.eventBus$.pipe(
takeUntilDestroyed()
).subscribe(theme => {
this.paletteMap = this.themeMap.get(theme)!;
})
}


public getColor(colorName: TColorName): string {
return this.paletteMap.get(colorName)!;
}
}

The service installs a new color map every time the theme changes. And the eventBus$ bus will notify the charts that they need to be refreshed.

Switching themes affects charts, and they are styled via ColorsService
Switching themes affects charts, and they are styled via ColorsService

By introducing the ColorsService abstraction, we have solved the potential problem of hardcoding colors in each component of the graph.

But we didn’t get rid of another problem — SCSS color variables and ColorsService are not related in any way, and therefore support for current and new themes will also be done by hardcoded colors. If the color scheme on SCSS changes, we’ll need to make changes here as well.

Can we make this ColorsService even more abstract, and not do any more theme support in it ?

Charts styling via CSS variables

CSS variables are properties like border or padding. We can access them in runtime via Javascript. It is enough to know the element on which our CSS variables are based. I’d like to use this property to solve a current problem.

Let’s expand our CSS variables by adding styles for the chart

:root {
[data-theme="default"] {
--primary: #d75894;
--secondary: #9b3dca;
--background: #fafafa;
+ --chart-color-1: #526ed3;
+ --chart-color-2: #ea97c4;
+ --chart-color-3: #fee797;
+ --base-1: #808080;
+ --base-2: #bebebe;
}

[data-theme="dark"] {
--primary: #5e84ff;
--secondary: #0dd0ff;
--background: #1b1918;
+ --chart-color-1: #ffce56;
+ --chart-color-2: #36a2eb;
+ --chart-color-3: #ff6384;
+ --base-1: #c7c7c7;
+ --base-2: #bcbcbc;
}
}

The new values are created from scratch, but can also rely on existing CSS variables

Let’s also change ColorsService. Instead of designating all possible presets for each theme, we’ll create a single color map with default values. At each theme change event, we will refer to the element for a new value and set it.

export class ColorsCssService {
private readonly paletteMap = new Map<TCssPropName<TColorName>, string>([
['--primary', '#d75894'],
['--secondary', '#9b3dca'],
['--chart-color-1', '#526ed3'],
['--chart-color-2', '#ea97c4'],
['--chart-color-3', '#fee797'],
['--base-1', '#808080'],
['--base-2', '#bebebe'],
])

constructor(
private themeSwitcher: ThemeSwitcherService,
@Inject(WINDOW)
private window: Window,
) {
this.themeSwitcher.eventBus$.pipe(
takeUntilDestroyed()
).subscribe(() => {
const styles: CSSStyleDeclaration = this.window.getComputedStyle(this.themeSwitcher.hostEl);

for (const cssColorProp of this.paletteMap.keys()) {
const cssColorValue = styles.getPropertyValue(cssColorProp);
if (!cssColorValue) {
continue
}

this.paletteMap.set(cssColorProp, cssColorValue)
}
})
}

public getColor(colorName: TColorName): string {
return this.paletteMap.get(`--${colorName}`)!;
}
}

Let’s check the result

Switching themes affects charts, and they are styled via ColorsService and CSS variables
Switching themes affects charts, and they are styled via ColorsService and CSS variables

Everything works fine as before 🔥.

New theme

Let’s add one more color theme

[data-theme="new-theme"] {
--primary: #eced00;
--secondary: #cfbe00;
--background: #001257;
--chart-color-1: #ff0000;
--chart-color-2: #ff5151;
--chart-color-3: #ff8b8b;
--base-1: #c7c7c7;
--base-2: #bcbcbc;
}

If everything is correct, we won’t need to make any changes to the current code and everything will work by itself.

Result

Switching themes affects charts via CSS variables
Switching themes affects charts via CSS variables

Conclusion

CSS variables are a powerful styling tool. Not only can it be used to manipulate css styles, and control the component, but it can also be a Store that we can access to get data in runtime.

It’s not hard to guess that if we provide our graph components via a library, customizing them will be simple and easy to maintain, as well as providing a convenient open-closed principle.

Resources

Other parts

Links

--

--