Charts with SVGs in Angular2+

Many people think about Angular for just HTML components and forms. After all, Angular1 was insanely easy and awesome to build 2-way bound forms. But what if we took all those same awesome concepts of components and use it to build awesome visualizations too? There are a few different ways we can render charts in the browser today; SVGs, Canvas and WebGL. In this article, I’m going to talk about building SVG driven charts, that particular one works REALLY awesome with frameworks like Angular because after all SVGs expressed via the DOM.

I recently had the opportunity open-source a project I’ve been working on for years and when we released it we re-wrote it in Angular2. Its a awesome framework called ngx-charts, if you haven’t checked it out I highly recommend but I could be bias ;P. We didn’t set out to build a full fledged charting framework at first but ya know it just kinda happens over time :)! We had the opportunity to talk about the project on AngularAir recently, its a great watch! I’ll cover some of the same topics here with a tad more detail.

There are a number of AWESOME charting frameworks out there like: HighCharts, Chart.js and who could forget the best of them all D3.js! Problem is when we went to build a custom visualization, they were difficult to piece them all together. There were other problems too, they violated the single point of DOM contact policy. <What does that mean? Because those frameworks are not Angular2+ code, they would be touching the DOM independently. This can cause problems when both Angular and the chart framework is manipulating the DOM simultaneously. You also can’t do all the cool stuff like AoT, Universal or NativeScript. With ngx-charts we crafted a chart framework that leverages Angular for all the rendering but uses D3 for all the math and utilities it has. A awesome combo!

In order to build a complex set of reusable visualizations like this you have to break every chart down to its bare bone concept. So take a bar chart like this:

In this chart, we have the following components:

  • Bars
  • X-Axis
  • Y-Axis
  • Legend
  • Grid lines
  • Labels
  • Tooltips on hover

then there is the actual chart itself that glues all this together. That’s a lot of stuff! Most people would build each one of these components in one huge component but what happens when you want to re-use it for a horizontal bar chart then? Or you want to re-use the grid lines in a line chart? Because we broke it all the way down to its core, you can do that! It creates truly re-usable charts!

We provide a lot of charts out of the box but its impossible to cover every case. Let's take a look at what building a custom bar chart would look like in ngx-charts:

<ngx-charts-chart  
[view]="[width, height]"
[showLegend]="false">
<svg:g class="bar-chart chart">
<svg:g
ngx-charts-series-vertical
[xScale]="xScale"
[yScale]="yScale"
[colors]="colors"
[series]="results"
[dims]="dims">
</svg:g>
</svg:g>
</ngx-charts-chart>

In the above example, you can see we wrap the component in a ngx-charts-chart component. This is the glue’er of all the child SVG components. It includes things like legend, calculating dimensions and other misc stuff. Next we define a svg:g group that also contains an attribute component called ngx-charts-series-vertical. You’ll notice we used the svg:g DOM element here versus our own component selector. That’s because the DOM spec is very strict about the hierarchy of SVG elements. In Angular1, we could use the replace attribute in the directive but that's gone in Angular2 so instead we will use a component attribute selector.

The vertical series component is the ‘meat’ of this component, it contains:

  • Set of bar components
  • X-Axis Scale

The TypeScript code in the component looks a little more like what you might find in a D3 component (after all we do use that under the hood).

import { Component, Input, OnChanges } from '@angular/core';
import { BaseChartComponent, ColorHelper } from 'ngx-charts';
import * as d3 from 'd3';
@Component({
selector: 'custom-chart',
templateUrl: './custom.component.html',
styleUrls: ['./custom.component.css']
})
export class CustomChartComponent extends BaseChartComponent implements OnChanges {
  dims: any;
xScale: any;
yScale: any;
xDomain: any;
yDomain: any;
colors: ColorHelper;
colorScheme: any = 'cool';
  @Input() view;
@Input() results;
  ngOnChanges() {
this.update();
}
  update() {
super.update();
    this.dims = {
width: this.width,
height: this.height
};
    this.xScale = this.getXScale();
this.yScale = this.getYScale();
    this.setColors();
}
  getXScale() {
const spacing = 0.2;
this.xDomain = this.getXDomain();
return d3.scaleBand()
.rangeRound([0, this.dims.width])
.paddingInner(spacing)
.domain(this.xDomain);
}
  getYScale() {
this.yDomain = this.getYDomain();
return d3.scaleLinear()
.range([this.dims.height, 0])
.domain(this.yDomain);
}
  getXDomain() {
return this.results.map(d => d.name);
}
  getYDomain() {
let values = this.results.map(d => d.value);
let min = Math.min(0, ...values);
let max = Math.max(...values);
return [min, max];
}
  setColors() {
this.colors = new ColorHelper(this.colorScheme, 'ordinal', this.xDomain);
}
}

That’s it for a totally custom bar chart. You have access to all the SVG elements here to manipulate those however you want. If you want to get down and dirty you can even roll your own series components. When we start to implement it, it ends up being a very clean and composable component in our application view:

import { Component } from '@angular/core';
@Component({
selector: 'app',
template: `
<div>
<h2>Custom Chart</h2>
<custom-chart
[view]="[600,300]"
[results]="data">
</custom-chart>
</div>
`
})
export class AppComponent {
  colorScheme = {
domain: ['#F44336', '#3F51B5', '#8BC34A', '#2196F3', '#009688', '#FF5722', '#CDDC39', '#00BCD4', '#FFC107', '#795548', '#607D8B']
};
  data = [
{
"name": "Germany",
"value": 46268
},
{
"name": "USA",
"value": 53041
},
{
"name": "France",
"value": 42503
},
{
"name": "United Kingdom",
"value": 41787
},
{
"name": "Spain",
"value": 29863
},
{
"name": "Italy",
"value": 35925
}
];
}

There’s so many cool things you can start doing when you start thinking about how you can use Angular in different ways beyond the mindset of what we were accustomed to in Angular1. If you're interested to see the above code running, check out this Github repo.

I’m #ngPanda

I hope you enjoyed the post, if you liked it follow me on Twitter and Github for more JavaScript tips/opinions/projects/articles/etc!