CSS variables in Angular: Part 3. Charts
Use CSS variables for Canvas and SVG charts
Previous parts
- CSS variables in Angular: Part 1. Forget about SCSS variables
- CSS variables in Angular: Part 2. ng-deep is no longer needed
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?
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.
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
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
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
- CSS variables in Angular: Part 1. Forget about SCSS variables
- CSS variables in Angular: Part 2. ng-deep is no longer needed